├── .husky └── pre-commit ├── .gitignore ├── scripts ├── .prettierrc.yaml └── create-toc.ts ├── .prettierignore ├── UltiSnips ├── javascriptreact.snippets ├── typescriptreact.snippets ├── scss.snippets ├── javascript.snippets └── typescript.snippets ├── stylua.toml ├── lua ├── stylua.toml ├── vim-react-snippets │ ├── luasnippets │ │ ├── javascriptreact.lua │ │ ├── mdx.lua │ │ ├── typescriptreact.lua │ │ ├── typescript.lua │ │ ├── scss.lua │ │ └── javascript.lua │ ├── config.lua │ ├── exports.lua │ ├── typescript.lua │ ├── jsdoc.lua │ ├── init.lua │ ├── logging.lua │ ├── redux.lua │ ├── imports.lua │ ├── react-testing.lua │ ├── testing.lua │ ├── test-queries.lua │ ├── react-prop-types.lua │ ├── common.lua │ ├── util.lua │ ├── react-hooks.lua │ └── react-components.lua └── .neoconf.json ├── .prettierrc.yaml ├── .neoconf.json ├── tsconfig.json ├── package.json ├── LICENSE ├── README.md └── pnpm-lock.yaml /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm lint-staged -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_STORE 3 | -------------------------------------------------------------------------------- /scripts/.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | semi: true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | pnpm*.yaml 4 | -------------------------------------------------------------------------------- /UltiSnips/javascriptreact.snippets: -------------------------------------------------------------------------------- 1 | extends javascript 2 | -------------------------------------------------------------------------------- /UltiSnips/typescriptreact.snippets: -------------------------------------------------------------------------------- 1 | extends typescript 2 | -------------------------------------------------------------------------------- /stylua.toml: -------------------------------------------------------------------------------- 1 | indent_type = "Spaces" 2 | indent_width = 2 3 | column_width = 120 -------------------------------------------------------------------------------- /lua/stylua.toml: -------------------------------------------------------------------------------- 1 | indent_type = "Spaces" 2 | indent_width = 2 3 | column_width = 120 -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | semi: false 2 | trailingComma: es5 3 | overrides: 4 | - files: "*.md" 5 | options: 6 | proseWrap: always 7 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/luasnippets/javascriptreact.lua: -------------------------------------------------------------------------------- 1 | local util = require("vim-react-snippets.util") 2 | 3 | return util.extend_language("javascript", {}) 4 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/luasnippets/mdx.lua: -------------------------------------------------------------------------------- 1 | local util = require("vim-react-snippets.util") 2 | 3 | local imports = require("vim-react-snippets.imports") 4 | 5 | return util.merge_lists(imports()) 6 | -------------------------------------------------------------------------------- /.neoconf.json: -------------------------------------------------------------------------------- 1 | { 2 | "neodev": { 3 | "library": { 4 | "enabled": true, 5 | "plugins": true 6 | } 7 | }, 8 | "neoconf": { 9 | "plugins": { 10 | "lua_ls": { 11 | "enabled": true 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lua/.neoconf.json: -------------------------------------------------------------------------------- 1 | { 2 | "neodev": { 3 | "library": { 4 | "enabled": true, 5 | "plugins": true 6 | } 7 | }, 8 | "neoconf": { 9 | "plugins": { 10 | "lua_ls": { 11 | "enabled": true 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/luasnippets/typescriptreact.lua: -------------------------------------------------------------------------------- 1 | local util = require("vim-react-snippets.util") 2 | local react_components = require("vim-react-snippets.react-components") 3 | local react_testing = require("vim-react-snippets.react-testing") 4 | local react_prop_types = require("vim-react-snippets.react-prop-types") 5 | 6 | return util.extend_language( 7 | "typescript", 8 | util.merge_lists(react_components(true), react_prop_types(true), react_testing(true)) 9 | ) 10 | -------------------------------------------------------------------------------- /UltiSnips/scss.snippets: -------------------------------------------------------------------------------- 1 | snippet use "use file/package" b 2 | @use "$1"; 3 | endsnippet 4 | 5 | snippet use* "use file/package as *" b 6 | @use "$1" as *; 7 | endsnippet 8 | 9 | 10 | snippet forw "forward package with" b 11 | @forward "$1" with ( 12 | $2 13 | ); 14 | endsnippet 15 | 16 | snippet imp "import package (legacy)" b 17 | @import "$1"; 18 | endsnippet 19 | 20 | snippet pcs "prefers-color-scheme media query" b 21 | @media (prefers-color-scheme: ${1:dark}) { 22 | $2 23 | } 24 | endsnippet 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "nodenext", 5 | "strict": true, 6 | "noEmit": true, 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "moduleResolution": "nodenext", 10 | "resolveJsonModule": true, 11 | "downlevelIteration": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "allowSyntheticDefaultImports": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "jsx": "preserve", 16 | "lib": ["esnext"] 17 | }, 18 | "exclude": ["node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/config.lua: -------------------------------------------------------------------------------- 1 | return { 2 | --- Set to `false` if all props should no longer be wrapped in 3 | --- `Readonly`. 4 | --- @type boolean 5 | readonly_props = true, 6 | 7 | --- @type string The test framework for the react test snippets. Can be set 8 | --- to `"vitest"` 9 | test_framework = "@jest/globals", 10 | 11 | --- The import path for `import { render, screen, userEvent }`. 12 | --- If you use a custom test renderer, set it to this config option. 13 | --- i.e. `"@/test-utils.js"` 14 | --- @type string 15 | test_renderer_path = "@testing-library/react", 16 | } 17 | -------------------------------------------------------------------------------- /scripts/create-toc.ts: -------------------------------------------------------------------------------- 1 | import { readFile, writeFile } from "node:fs/promises"; 2 | import { format } from "prettier"; 3 | import toc from "markdown-toc"; 4 | import { join } from "node:path"; 5 | 6 | const root = process.cwd(); 7 | const README = join(root, "README.md"); 8 | 9 | const original = await readFile(README, "utf8"); 10 | 11 | console.log("Updating table of contents in README.md"); 12 | const updated = await format(toc.insert(original), { 13 | parser: "markdown", 14 | semi: false, 15 | trailingComma: "none", 16 | }); 17 | const removedEmojisFromLinks = updated.replace(/%E2%9C%A8/g, ""); 18 | 19 | await writeFile(README, removedEmojisFromLinks); 20 | console.log("Updated!"); 21 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/luasnippets/typescript.lua: -------------------------------------------------------------------------------- 1 | local util = require("vim-react-snippets.util") 2 | 3 | local common = require("vim-react-snippets.common") 4 | local exports = require("vim-react-snippets.exports") 5 | local imports = require("vim-react-snippets.imports") 6 | local jsdoc = require("vim-react-snippets.jsdoc") 7 | local logging = require("vim-react-snippets.logging") 8 | local react_hooks = require("vim-react-snippets.react-hooks") 9 | local redux = require("vim-react-snippets.redux") 10 | local test_queries = require("vim-react-snippets.test-queries") 11 | local testing = require("vim-react-snippets.testing") 12 | local typescript = require("vim-react-snippets.typescript") 13 | 14 | return util.merge_lists( 15 | imports(), 16 | exports(), 17 | logging(), 18 | common(true), 19 | jsdoc(true), 20 | react_hooks(true), 21 | redux(true), 22 | testing(), 23 | test_queries(), 24 | typescript() 25 | ) 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vim-react-snippets", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "description": "Reusable Javascript and Typescript vim snippets for UltiSnips and developing React apps.", 6 | "repository": "git@github.com:mlaursen/vim-react-snippets.git", 7 | "author": "Mikkel Laursen ", 8 | "license": "Apache-2.0", 9 | "private": true, 10 | "scripts": { 11 | "prepare": "husky", 12 | "create-toc": "tsx scripts/create-toc.ts" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^22.14.0", 16 | "husky": "^9.1.7", 17 | "lint-staged": "^15.5.0", 18 | "markdown-toc": "^1.2.0", 19 | "prettier": "^3.5.3", 20 | "tsx": "^4.19.3", 21 | "typescript": "^5.8.2" 22 | }, 23 | "volta": { 24 | "node": "22.14.0", 25 | "pnpm": "10.7.1" 26 | }, 27 | "lint-staged": { 28 | "README.md": [ 29 | "pnpm run create-toc" 30 | ], 31 | "**/*.{ts,js}": [ 32 | "prettier --write" 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/exports.lua: -------------------------------------------------------------------------------- 1 | local util = require("vim-react-snippets.util") 2 | local ls = require("luasnip") 3 | 4 | local s = ls.snippet 5 | local t = ls.text_node 6 | local i = ls.insert_node 7 | 8 | local exports = function() 9 | return { 10 | s({ 11 | trig = "exp", 12 | name = "Export Default", 13 | }, { 14 | t('export { default } from "./'), 15 | util.current_filename(1), 16 | t('"'), 17 | }), 18 | s({ 19 | trig = "exf", 20 | name = "Export File", 21 | }, { 22 | t("export "), 23 | i(1), 24 | t(' from "'), 25 | i(2, "./"), 26 | util.mirror_node(1), 27 | t('"'), 28 | }), 29 | s({ 30 | trig = "expd", 31 | name = "Export Destructured", 32 | }, { 33 | t("export { "), 34 | i(1), 35 | t(' } from "'), 36 | i(2), 37 | t('"'), 38 | }), 39 | s({ 40 | trig = "expa", 41 | name = "Export All", 42 | }, { 43 | t('export * from "'), 44 | i(1), 45 | t('"'), 46 | }), 47 | } 48 | end 49 | 50 | return exports 51 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/luasnippets/scss.lua: -------------------------------------------------------------------------------- 1 | local ls = require("luasnip") 2 | 3 | local s = ls.snippet 4 | local t = ls.text_node 5 | local i = ls.insert_node 6 | 7 | return { 8 | s({ 9 | trig = "use", 10 | name = "Use File or Package", 11 | }, { 12 | t('@use "'), 13 | i(1), 14 | t('";'), 15 | }), 16 | s({ 17 | trig = "use*", 18 | name = "Use File or Package as *", 19 | }, { 20 | t('@use "'), 21 | i(1), 22 | t('" as *;'), 23 | }), 24 | s({ 25 | trig = "for", 26 | name = "Forward", 27 | }, { 28 | t('@forward "'), 29 | i(1), 30 | t({ '" with (', "\t" }), 31 | i(2), 32 | t({ "", ");" }), 33 | }), 34 | 35 | -- not used often 36 | s({ 37 | trig = "pcs", 38 | name = "Prefers Color Scheme", 39 | }, { 40 | t("@media (prefers-color-scheme: "), 41 | i(1, "dark"), 42 | t({ ") {", "\t" }), 43 | i(2), 44 | t({ "", "}" }), 45 | }), 46 | 47 | -- legacy 48 | s({ 49 | trig = "imp", 50 | name = "Import Package (Legacy)", 51 | }, { 52 | t('@import "'), 53 | i(1), 54 | t('";'), 55 | }), 56 | } 57 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/luasnippets/javascript.lua: -------------------------------------------------------------------------------- 1 | local util = require("vim-react-snippets.util") 2 | 3 | local common = require("vim-react-snippets.common") 4 | local exports = require("vim-react-snippets.exports") 5 | local imports = require("vim-react-snippets.imports") 6 | local jsdoc = require("vim-react-snippets.jsdoc") 7 | local logging = require("vim-react-snippets.logging") 8 | local react_prop_types = require("vim-react-snippets.react-prop-types") 9 | local react_hooks = require("vim-react-snippets.react-hooks") 10 | local react_components = require("vim-react-snippets.react-components") 11 | local react_testing = require("vim-react-snippets.react-testing") 12 | local redux = require("vim-react-snippets.redux") 13 | local test_queries = require("vim-react-snippets.test-queries") 14 | local testing = require("vim-react-snippets.testing") 15 | 16 | return util.merge_lists( 17 | imports(), 18 | exports(), 19 | logging(), 20 | common(false), 21 | jsdoc(false), 22 | react_hooks(false), 23 | redux(false), 24 | testing(), 25 | test_queries(), 26 | react_components(false), 27 | react_testing(false), 28 | react_prop_types(false) 29 | ) 30 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/typescript.lua: -------------------------------------------------------------------------------- 1 | local util = require("vim-react-snippets.util") 2 | 3 | local ls = require("luasnip") 4 | 5 | local s = ls.snippet 6 | local sn = ls.snippet_node 7 | local t = ls.text_node 8 | local i = ls.insert_node 9 | local d = ls.dynamic_node 10 | 11 | local typescript = function() 12 | return { 13 | s({ 14 | trig = "intf", 15 | name = "Interface", 16 | }, { 17 | t("export interface "), 18 | util.current_filename(1), 19 | i(2), 20 | t({ " {", "\t" }), 21 | i(3), 22 | t({ "", "}" }), 23 | }), 24 | s({ 25 | trig = ""), 37 | }), 38 | s({ 39 | trig = ""), 48 | }), 49 | } 50 | end 51 | 52 | return typescript 53 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/jsdoc.lua: -------------------------------------------------------------------------------- 1 | local ls = require("luasnip") 2 | 3 | local s = ls.snippet 4 | local sn = ls.snippet_node 5 | local t = ls.text_node 6 | local i = ls.insert_node 7 | 8 | --- @param typescript boolean 9 | --- @return unknown[] 10 | local jsdoc = function(typescript) 11 | return { 12 | s({ 13 | trig = "/**", 14 | name = "JSDoc Comment", 15 | }, { 16 | sn(1, { 17 | t({ "/**", " * " }), 18 | i(1), 19 | t({ "", " */" }), 20 | }), 21 | }), 22 | 23 | s({ 24 | trig = "@e", 25 | name = "@example", 26 | }, { 27 | t("@example "), 28 | i(1, "Example Name"), 29 | t({ "", "" }), 30 | t("* ```"), 31 | i(2, "tsx"), 32 | t({ "", "* " }), 33 | i(3), 34 | t({ "", "* ```" }), 35 | }), 36 | 37 | s({ 38 | trig = "@d", 39 | name = "@default" .. (typescript and "Value" or ""), 40 | }, { 41 | t("@default" .. (typescript and "Value" or "") .. " `"), 42 | i(1), 43 | t("`"), 44 | }), 45 | 46 | s({ 47 | trig = "@s", 48 | name = "@since major.minor.patch", 49 | }, { 50 | t("@since "), 51 | i(1, "1"), 52 | t("."), 53 | i(2, "0"), 54 | t("."), 55 | i(3, "0"), 56 | }), 57 | } 58 | end 59 | 60 | return jsdoc 61 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/init.lua: -------------------------------------------------------------------------------- 1 | local util = require("vim-react-snippets.util") 2 | local config = require("vim-react-snippets.config") 3 | 4 | local M = {} 5 | 6 | --- @class vim-react-snippets.SetupOptions 7 | --- @field readonly_props? boolean Set to `false` if all props should no longer be wrapped in `Readonly`. 8 | --- @field test_framework? string The test framework for the react test snippets. Can be set to `"vitest"` 9 | --- @field test_renderer_path? string The import path for `import { render, screen, userEvent }`. If you use a custom test renderer, set it to this config option. i.e. `"@/test-utils.js"` 10 | --- @field load? boolean Set to `false` to prevent loading the snippets with `require("luasnip.loaders.from_lua").load()` 11 | 12 | --- @param opts vim-react-snippets.SetupOptions 13 | M.setup = function(opts) 14 | opts = opts or {} 15 | 16 | if opts.readonly_props == true or opts.readonly_props == false then 17 | config.readonly_props = opts.readonly_props 18 | end 19 | 20 | if type(opts.test_framework) == "string" then 21 | config.test_framework = opts.test_framework 22 | end 23 | 24 | if type(opts.test_renderer_path) == "string" then 25 | config.test_renderer_path = opts.test_renderer_path 26 | end 27 | 28 | if opts.load ~= false then 29 | util.load() 30 | end 31 | end 32 | 33 | return M 34 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/logging.lua: -------------------------------------------------------------------------------- 1 | local util = require("vim-react-snippets.util") 2 | 3 | local ls = require("luasnip") 4 | 5 | local s = ls.snippet 6 | local t = ls.text_node 7 | local i = ls.insert_node 8 | 9 | --- @private 10 | --- @alias vim-react-snippets.ConsoleType 'log' | 'error' | 'warn' | 'debug' | 'table' 11 | 12 | --- @param type vim-react-snippets.ConsoleType 13 | --- @param mirror boolean 14 | local console = function(type, mirror) 15 | local trig = "c" .. type:sub(1, 1) .. (mirror and "v" or "") 16 | local body = (not mirror and { i(1) }) or { 17 | t('"'), 18 | util.mirror_node(1), 19 | t(':", '), 20 | i(1), 21 | } 22 | return s( 23 | { trig = trig }, 24 | util.merge_lists( 25 | { 26 | t("console." .. type .. "("), 27 | }, 28 | body, 29 | { 30 | t(")"), 31 | } 32 | ) 33 | ) 34 | end 35 | 36 | local logging = function() 37 | --- @type vim-react-snippets.ConsoleType[] 38 | local types = { "log", "error", "warn", "debug", "table" } 39 | local false_true = { false, true } 40 | 41 | local snippets = {} 42 | for _, mirror in pairs(false_true) do 43 | for _, type in pairs(types) do 44 | if type ~= "table" or not mirror then 45 | table.insert(snippets, console(type, mirror)) 46 | end 47 | end 48 | end 49 | 50 | return snippets 51 | end 52 | 53 | return logging 54 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/redux.lua: -------------------------------------------------------------------------------- 1 | local util = require("vim-react-snippets.util") 2 | 3 | local ls = require("luasnip") 4 | 5 | local sn = ls.snippet_node 6 | local t = ls.text_node 7 | local i = ls.insert_node 8 | 9 | --- @param typescript boolean 10 | --- @return unknown[] 11 | local use_app_selector = function(typescript) 12 | if not typescript then 13 | return {} 14 | end 15 | 16 | return util.const_snippet({ 17 | config = { 18 | trig = "useAS", 19 | name = "useAppSelector", 20 | }, 21 | const_name = "value", 22 | const_edit = 1, 23 | create_snippet = function(start) 24 | return { 25 | t("useAppSelector("), 26 | i(start, "state"), 27 | t(")"), 28 | } 29 | end, 30 | }) 31 | end 32 | 33 | --- @param typescript boolean 34 | --- @return unknown[] 35 | local redux = function(typescript) 36 | return util.merge_lists( 37 | util.const_snippet({ 38 | config = { 39 | trig = "useD", 40 | name = "useDispatch", 41 | }, 42 | const_name = "dispatch", 43 | const_edit = 1, 44 | create_snippet = function() 45 | return { 46 | t("use" .. (typescript and "App" or "") .. "Dispatch()"), 47 | } 48 | end, 49 | }), 50 | 51 | use_app_selector(typescript), 52 | 53 | util.const_snippet({ 54 | config = { 55 | trig = "useSL", 56 | name = "useSelector", 57 | }, 58 | const_name = "value", 59 | const_edit = 1, 60 | create_snippet = function(start) 61 | local add = 0 62 | local typedef = {} 63 | if typescript then 64 | add = add + 1 65 | table.insert(typedef, t(": ")) 66 | table.insert(typedef, sn(start, { i(1, "AppState") })) 67 | end 68 | 69 | return util.merge_lists( 70 | { 71 | t("useSelector((state"), 72 | }, 73 | typedef, 74 | { 75 | t(") => "), 76 | i(start + add, ""), 77 | t(")"), 78 | } 79 | ) 80 | end, 81 | }) 82 | ) 83 | end 84 | 85 | return redux 86 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/imports.lua: -------------------------------------------------------------------------------- 1 | local util = require("vim-react-snippets.util") 2 | 3 | local ls = require("luasnip") 4 | 5 | local s = ls.snippet 6 | local sn = ls.snippet_node 7 | local t = ls.text_node 8 | local i = ls.insert_node 9 | local d = ls.dynamic_node 10 | 11 | local imports = function() 12 | return { 13 | s({ 14 | trig = "imp", 15 | name = "Import Package", 16 | desc = "import anything from package", 17 | }, { 18 | t("import "), 19 | i(1), 20 | t(' from "'), 21 | util.editable_mirror_node(2, { 1 }), 22 | t('"'), 23 | }), 24 | s({ 25 | trig = "impa", 26 | name = "Import All", 27 | desc = "import * as something from package", 28 | }, { 29 | t("import * as "), 30 | i(1), 31 | t(' from "'), 32 | util.editable_mirror_node(2, { 1 }), 33 | t('"'), 34 | }), 35 | s({ 36 | trig = "impf", 37 | name = "Import File", 38 | }, { 39 | t("import "), 40 | i(1), 41 | t(' from "'), 42 | d(2, function(args) 43 | return sn(nil, { i(1, "./"), i(2, args[1]) }) 44 | end, { 1 }), 45 | t('"'), 46 | }), 47 | s({ 48 | trig = "impd", 49 | name = "Import Destructured", 50 | desc = "import { destructured } from package", 51 | }, { 52 | t("import { "), 53 | i(1), 54 | t(' } from "'), 55 | i(2), 56 | t('"'), 57 | }), 58 | s({ 59 | trig = "impp", 60 | name = 'import "package-name"', 61 | desc = "Import a package that only has side effects", 62 | }, { 63 | t('import "'), 64 | i(1, ""), 65 | t('"'), 66 | }), 67 | s({ 68 | trig = "icn", 69 | name = "Import Classnames", 70 | desc = "import classnames as cn", 71 | }, { 72 | t('import cn from "classnames"'), 73 | }), 74 | s({ 75 | trig = "icnb", 76 | name = "Import cnbuilder", 77 | desc = "import cnbuilder as cnb", 78 | }, { 79 | t('import { cnb } from "cnbuilder"'), 80 | }), 81 | s({ 82 | trig = "ism", 83 | name = "Import SCSS Module", 84 | desc = "import scss module", 85 | }, { 86 | t("import "), 87 | i(2, "styles"), 88 | t(' from "./'), 89 | util.current_filename(1), 90 | t('.module.scss"'), 91 | }), 92 | } 93 | end 94 | 95 | return imports 96 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/react-testing.lua: -------------------------------------------------------------------------------- 1 | local util = require("vim-react-snippets.util") 2 | local config = require("vim-react-snippets.config") 3 | 4 | local ls = require("luasnip") 5 | 6 | local s = ls.snippet 7 | local t = ls.text_node 8 | local i = ls.insert_node 9 | 10 | --- @param esm boolean 11 | --- @param globals boolean 12 | local react_test_file = function(esm, globals) 13 | local rtl_index = (globals and 2) or 1 14 | local filename_index = rtl_index + 1 15 | local globals_import = globals 16 | and { 17 | t('import { describe, expect, it } from "'), 18 | i(1, config.test_framework), 19 | t({ '"', "" }), 20 | } 21 | or {} 22 | 23 | return s( 24 | { 25 | trig = (globals and "g" or "r") .. "tf" .. (esm and "e" or ""), 26 | name = (globals and "Globals" or "React") .. "Test File" .. (esm and " (ESM)" or ""), 27 | desc = "Base react testing setup with react-testing-library" 28 | .. (globals and " and jest globals" or "") 29 | .. (esm and " and esm imports" or ""), 30 | }, 31 | util.merge_lists(globals_import, { 32 | t('import { render, screen, userEvent } from "'), 33 | i(rtl_index, config.test_renderer_path), 34 | t({ '"', "" }), 35 | t("import { "), 36 | util.current_filename(filename_index), 37 | t(' } from "../'), 38 | util.mirror_node(filename_index), 39 | t(esm and ".js" or ""), 40 | t({ '"', "", "" }), 41 | 42 | t('describe("'), 43 | util.mirror_node(filename_index), 44 | t({ '", () => {', '\tit("should ' }), 45 | i(filename_index + 1), 46 | t({ '", () => {', "\t\t" }), 47 | i(filename_index + 2), 48 | t({ "", "\t})", "})" }), 49 | }) 50 | ) 51 | end 52 | 53 | --- @param typescript boolean 54 | --- @return unknown[] 55 | local react_testing = function(typescript) 56 | return { 57 | react_test_file(false, false), 58 | react_test_file(false, true), 59 | react_test_file(true, false), 60 | react_test_file(true, true), 61 | 62 | s({ 63 | trig = "uet", 64 | name = "User Event Test", 65 | }, { 66 | t('it("should '), 67 | i(1), 68 | t({ '", async () => {', "\t" }), 69 | t({ "const user = userEvent.setup()", "\t" }), 70 | i(2), 71 | t({ "", "\texpect(true).toBe(true)" }), 72 | t({ "", "})" }), 73 | }), 74 | } 75 | end 76 | 77 | return react_testing -------------------------------------------------------------------------------- /lua/vim-react-snippets/testing.lua: -------------------------------------------------------------------------------- 1 | local ls = require("luasnip") 2 | 3 | local s = ls.snippet 4 | local t = ls.text_node 5 | local i = ls.insert_node 6 | 7 | local testing = function() 8 | return { 9 | s({ 10 | trig = "desc", 11 | name = "Describe", 12 | desc = "Describe a test", 13 | }, { 14 | t('describe("'), 15 | i(1), 16 | t({ '", () => {', '\tit("should ' }), 17 | i(2), 18 | t({ '", () => {', "\t\t" }), 19 | i(3), 20 | t({ "", "\t})", "})" }), 21 | }), 22 | s({ 23 | trig = "it", 24 | name = "it", 25 | desc = 'it("should ...")', 26 | }, { 27 | t('it("should '), 28 | i(1), 29 | t({ '", () => {', "\t" }), 30 | i(0), 31 | t({ "", "})" }), 32 | }), 33 | s({ 34 | trig = "ita", 35 | name = "it (async)", 36 | desc = 'it("should ...", async () => { ... })', 37 | }, { 38 | t('it("should '), 39 | i(1), 40 | t({ '", async () => {', "\t" }), 41 | i(0), 42 | t({ "", "})" }), 43 | }), 44 | s({ 45 | trig = "es", 46 | name = "Expect Snapshot", 47 | desc = "", 48 | }, { 49 | t("expect("), 50 | i(1, "container"), 51 | t(").toMatchSnapshot()"), 52 | }), 53 | 54 | s({ 55 | trig = "wf", 56 | name = "wait for", 57 | }, { 58 | t({ "await waitFor(() => {", "\t" }), 59 | i(0), 60 | t({ "", "})" }), 61 | }), 62 | 63 | s({ 64 | trig = "ett", 65 | name = "expect to throw", 66 | }, { 67 | t("expect(() => "), 68 | i(1), 69 | t(").toThrow()"), 70 | }), 71 | s({ 72 | trig = "entt", 73 | name = "expect not to throw", 74 | }, { 75 | t("expect(() => "), 76 | i(1), 77 | t(").not.toThrow()"), 78 | }), 79 | 80 | s({ 81 | trig = "enc", 82 | name = "expect not called", 83 | desc = "expect not toHaveBeenCalled", 84 | }, { 85 | t("expect("), 86 | i(1), 87 | t(").not.toHaveBeenCalled()"), 88 | }), 89 | s({ 90 | trig = "ecw", 91 | name = "expect called with", 92 | desc = "expect toHaveBeenCalledWith", 93 | }, { 94 | t("expect("), 95 | i(1), 96 | t(").toHaveBeenCalledWith("), 97 | i(2), 98 | t(")"), 99 | }), 100 | s({ 101 | trig = "encw", 102 | name = "expect not called with", 103 | desc = "expect not toHaveBeenCalledWith", 104 | }, { 105 | t("expect("), 106 | i(1), 107 | t(").not.toHaveBeenCalledWith("), 108 | i(2), 109 | t(")"), 110 | }), 111 | } 112 | end 113 | 114 | return testing 115 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/test-queries.lua: -------------------------------------------------------------------------------- 1 | local util = require("vim-react-snippets.util") 2 | 3 | local ls = require("luasnip") 4 | local conds = require("luasnip.extras.expand_conditions") 5 | local s = ls.snippet 6 | local t = ls.text_node 7 | local i = ls.insert_node 8 | 9 | --- @private 10 | --- @class vim-react-snippets.ByTypeOptions 11 | --- @field type "role" | "role unnamed" | "testid" | "text" | "label" 12 | --- @field find boolean 13 | --- @field inline boolean 14 | --- @field screen boolean 15 | 16 | --- @param opts vim-react-snippets.ByTypeOptions 17 | local by_type = function(opts) 18 | local type = opts["type"] 19 | local find = opts["find"] 20 | local inline = opts["inline"] 21 | local screen = opts["screen"] 22 | 23 | local is_role = type == "role" 24 | local is_role_unnamed = type == "role unnamed" 25 | local role = (is_role_unnamed and "progressbar") or (is_role and "button") or "" 26 | local get_or_find = find and "find" or "get" 27 | local maybe_screen = screen and "screen." or "" 28 | local maybe_await = find and "await " or "" 29 | local by_type = (type == "testid" and "TestId") 30 | or (type == "text" and "Text") 31 | or (type == "label" and "LabelText") 32 | or "Role" 33 | local query = maybe_await .. maybe_screen .. get_or_find .. "By" .. by_type 34 | 35 | local start = 1 36 | local prefix = {} 37 | local trailing = {} 38 | if not inline then 39 | start = start + 1 40 | table.insert(prefix, t("const ")) 41 | table.insert(prefix, i(1)) 42 | table.insert(prefix, t(" = ")) 43 | end 44 | if is_role then 45 | table.insert(trailing, t(', { name: "')) 46 | table.insert(trailing, i(start + 1)) 47 | table.insert(trailing, t('" }')) 48 | end 49 | 50 | local char = type == "testid" and "i" or type:sub(1, 1) 51 | local trig = (screen and "s" or "") .. (find and "f" or "g") .. "b" .. char .. (is_role_unnamed and "u" or "") 52 | local name = (screen and "Screen " or "") 53 | .. (find and "Find" or "Get") 54 | .. " By " 55 | .. by_type 56 | .. (is_role_unnamed and " Unnamed" or "") 57 | local condition = not inline and conds.line_begin or nil 58 | 59 | return s( 60 | { 61 | trig = trig, 62 | name = name, 63 | }, 64 | util.merge_lists( 65 | prefix, 66 | { 67 | t(query .. '("'), 68 | (not inline and type == "testid" and util.editable_mirror_node(start, { 1 })) or i(start, role), 69 | t('"'), 70 | }, 71 | trailing, 72 | { 73 | t(")"), 74 | } 75 | ), 76 | { condition = condition } 77 | ) 78 | end 79 | 80 | local test_queries = function() 81 | local types = { "role", "role unnamed", "testid", "text", "label" } 82 | local false_true = { false, true } 83 | 84 | local snippets = {} 85 | for _, type in pairs(types) do 86 | for _, inline in pairs(false_true) do 87 | for _, screen in pairs(false_true) do 88 | for _, find in pairs(false_true) do 89 | table.insert( 90 | snippets, 91 | by_type({ 92 | type = type, 93 | find = find, 94 | inline = inline, 95 | screen = screen, 96 | }) 97 | ) 98 | end 99 | end 100 | end 101 | end 102 | 103 | return snippets 104 | end 105 | 106 | return test_queries 107 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/react-prop-types.lua: -------------------------------------------------------------------------------- 1 | local util = require("vim-react-snippets.util") 2 | 3 | local ls = require("luasnip") 4 | 5 | local s = ls.snippet 6 | local sn = ls.snippet_node 7 | local t = ls.text_node 8 | local i = ls.insert_node 9 | 10 | local prop_type = function(opts) 11 | local key = opts.key 12 | local type = opts.type 13 | local required = opts.required 14 | 15 | local defn = { 16 | t("PropTypes." .. type), 17 | } 18 | if opts.call then 19 | table.insert(defn, t("(")) 20 | local call = opts.call() 21 | for _, v in ipairs(call) do 22 | table.insert(defn, v) 23 | end 24 | table.insert(defn, t(")")) 25 | end 26 | 27 | local key_suffix = "" 28 | local type_suffix = "" 29 | if required then 30 | key_suffix = "r" 31 | type_suffix = ".isRequired" 32 | table.insert(defn, t(".isRequired")) 33 | end 34 | 35 | return s({ 36 | trig = "pt." .. key .. key_suffix, 37 | desc = "PropTypes." .. type .. type_suffix, 38 | }, defn) 39 | end 40 | 41 | local prop_types = function(opts) 42 | return { 43 | prop_type({ 44 | key = opts.key, 45 | call = opts.call, 46 | type = opts.type, 47 | }), 48 | prop_type({ 49 | key = opts.key, 50 | call = opts.call, 51 | type = opts.type, 52 | required = true, 53 | }), 54 | } 55 | end 56 | 57 | --- @param typescript boolean 58 | local react_prop_types = function(typescript) 59 | local extra = {} 60 | if not typescript then 61 | table.insert( 62 | extra, 63 | s({ 64 | trig = "cpt", 65 | desc = "component prop types", 66 | }, { 67 | util.current_filename(1), 68 | t({ ".propTypes = {", "" }), 69 | t({ "\t" }), 70 | sn(2, { 71 | i(1, "className"), 72 | }), 73 | t(": "), 74 | sn(3, { 75 | i(1, "PropTypes.string"), 76 | }), 77 | i(0), 78 | t({ "", "" }), 79 | t("}"), 80 | }) 81 | ) 82 | end 83 | 84 | return util.merge_lists( 85 | prop_types({ 86 | key = "a", 87 | type = "array", 88 | }), 89 | prop_types({ 90 | key = "b", 91 | type = "bool", 92 | }), 93 | prop_types({ 94 | key = "f", 95 | type = "func", 96 | }), 97 | prop_types({ 98 | key = "nu", 99 | type = "number", 100 | }), 101 | prop_types({ 102 | key = "o", 103 | type = "object", 104 | }), 105 | prop_types({ 106 | key = "s", 107 | type = "string", 108 | }), 109 | prop_types({ 110 | key = "no", 111 | type = "node", 112 | }), 113 | prop_types({ 114 | key = "e", 115 | type = "element", 116 | }), 117 | prop_types({ 118 | key = "ao", 119 | type = "arrayOf", 120 | call = function() 121 | return { i(1, "PropTypes.string") } 122 | end, 123 | }), 124 | prop_types({ 125 | key = "io", 126 | type = "instanceOf", 127 | call = function() 128 | return { i(1, "PropTypes.string") } 129 | end, 130 | }), 131 | prop_types({ 132 | key = "oo", 133 | type = "objectOf", 134 | call = function() 135 | return { i(1, "PropTypes.string") } 136 | end, 137 | }), 138 | prop_types({ 139 | key = "one", 140 | type = "oneOf", 141 | call = function() 142 | return { 143 | t("['"), 144 | i(1), 145 | t("'"), 146 | i(2), 147 | t("]"), 148 | } 149 | end, 150 | }), 151 | prop_types({ 152 | key = "onet", 153 | type = "oneOfType", 154 | call = function() 155 | return { 156 | t({ "[" }, { "" }), 157 | i(1), 158 | t({ "]" }), 159 | } 160 | end, 161 | }), 162 | prop_types({ 163 | key = "sh", 164 | type = "shape", 165 | call = function() 166 | return { 167 | t({ "{", "" }), 168 | t({ "\t" }), 169 | i(1), 170 | t({ "", "}" }), 171 | } 172 | end, 173 | }), 174 | extra 175 | ) 176 | end 177 | 178 | return react_prop_types 179 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/common.lua: -------------------------------------------------------------------------------- 1 | local util = require("vim-react-snippets.util") 2 | 3 | local ls = require("luasnip") 4 | local conds = require("luasnip.extras.expand_conditions") 5 | 6 | local s = ls.snippet 7 | local t = ls.text_node 8 | local i = ls.insert_node 9 | 10 | --- @param typescript boolean 11 | --- @return unknown 12 | local reduce = function(typescript) 13 | local result_index = 3 14 | --- @type unknown[] 15 | local result_type = {} 16 | if typescript then 17 | result_index = 4 18 | table.insert(result_type, t("<")) 19 | table.insert(result_type, i(3, "ResultType")) 20 | table.insert(result_type, t(">")) 21 | end 22 | 23 | local item_index = result_index + 1 24 | local body_index = item_index + 1 25 | local initial_index = body_index + 1 26 | 27 | return s( 28 | { 29 | trig = "reduce", 30 | name = "Reduce", 31 | }, 32 | util.merge_lists( 33 | { 34 | t("const "), 35 | i(1, "value"), 36 | t(" = "), 37 | i(2, "list"), 38 | t(".reduce"), 39 | }, 40 | result_type, 41 | { 42 | t("(("), 43 | i(result_index, "result"), 44 | t(", "), 45 | i(item_index, "item"), 46 | t({ ") => {", "\t" }), 47 | i(body_index), 48 | t({ "", "\t return " }), 49 | util.mirror_node(item_index), 50 | t({ "", "}, " }), 51 | i(initial_index, "initial"), 52 | t(")"), 53 | } 54 | ), 55 | { condition = conds.line_begin } 56 | ) 57 | end 58 | 59 | --- @param typescript boolean 60 | local common = function(typescript) 61 | return { 62 | s({ 63 | trig = "dev", 64 | name = "Development", 65 | desc = 'process.env.NODE_ENV !== "production"', 66 | }, { 67 | t('process.env.NODE_ENV !== "production"'), 68 | }), 69 | s({ 70 | trig = "prod", 71 | name = "Production", 72 | desc = 'process.env.NODE_ENV === "production"', 73 | }, { 74 | t('process.env.NODE_ENV === "production"'), 75 | }), 76 | 77 | s({ 78 | trig = "noop", 79 | }, { 80 | t("const noop = ()" .. (typescript and ": void" or "")), 81 | t({ " => {", "\t// do nothing", "}" }), 82 | }), 83 | 84 | s({ 85 | trig = "dc", 86 | name = "Destructured Const", 87 | }, { 88 | t("const { "), 89 | i(2), 90 | t(" } = "), 91 | i(1, "props"), 92 | }, { 93 | condition = conds.line_end, 94 | }), 95 | 96 | s({ 97 | trig = "dc", 98 | name = "Destructured Const", 99 | }, { 100 | t("const { "), 101 | i(1), 102 | t(" } = "), 103 | }), 104 | 105 | s({ 106 | trig = "edc", 107 | name = "Export Destructured Const", 108 | }, { 109 | t("export const { "), 110 | i(2), 111 | t(" } = "), 112 | i(1, "props"), 113 | }), 114 | 115 | s({ 116 | trig = "if", 117 | desc = "if (condition) {}", 118 | }, { 119 | t("if ("), 120 | i(1, "condition"), 121 | t({ ") {", "\t" }), 122 | i(2), 123 | t({ "", "}" }), 124 | i(0), 125 | }, { 126 | condition = conds.line_end, 127 | }), 128 | 129 | s({ 130 | trig = "if", 131 | desc = "if (condition) ", 132 | }, { 133 | t("if ("), 134 | i(0, "condition"), 135 | t(")"), 136 | }, { 137 | --- @param line_to_cursor string 138 | condition = function(line_to_cursor) 139 | return line_to_cursor:find("else%s*") ~= nil 140 | end, 141 | }), 142 | 143 | s({ 144 | trig = "else", 145 | name = "Else", 146 | desc = "else {}", 147 | }, { 148 | t("else "), 149 | i(1), 150 | t({ "{", "\t" }), 151 | i(2), 152 | t({ "", "}" }), 153 | i(0), 154 | }), 155 | 156 | s({ 157 | trig = "switch", 158 | name = "Switch Statement", 159 | }, { 160 | t("switch ("), 161 | i(1, "key"), 162 | t({ ") {", "\tcase " }), 163 | i(2, "value"), 164 | t({ ":", "\t\t" }), 165 | i(3), 166 | t({ "", "\t\tbreak", "\tdefault:", "\t\t" }), 167 | i(0), 168 | t({ "", "}", "" }), 169 | }), 170 | 171 | s({ 172 | trig = "for", 173 | name = "for loop", 174 | }, { 175 | t("for (let "), 176 | i(1, "i"), 177 | t(" = "), 178 | i(2, "0"), 179 | t("; "), 180 | util.mirror_node(1), 181 | t(" < "), 182 | i(3, "list"), 183 | t(".length; "), 184 | util.mirror_node(1), 185 | t({ "++ {", "\tconst " }), 186 | i(4, "item"), 187 | t(" = "), 188 | util.mirror_node(3), 189 | t("["), 190 | util.mirror_node(1), 191 | t({ "]", "\t" }), 192 | i(0), 193 | t({ "", "}" }), 194 | }), 195 | 196 | reduce(typescript), 197 | } 198 | end 199 | 200 | return common 201 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/util.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.extension_name = "vim-react-snippets" 4 | 5 | --- @param extension_name? string 6 | M.get_snippet_path = function(extension_name) 7 | extension_name = extension_name or M.extension_name 8 | local snippets = vim.api.nvim_get_runtime_file("lua/" .. extension_name .. "/init.lua", false)[1] 9 | if snippets == nil or snippets == "" then 10 | error("Unable to find " .. extension_name .. " path") 11 | end 12 | 13 | return (snippets:gsub("init.lua", "luasnippets")) 14 | end 15 | 16 | --- @param extension_name? string 17 | M.load = function(extension_name) 18 | require("luasnip.loaders.from_lua").load({ 19 | paths = { M.get_snippet_path(extension_name) }, 20 | }) 21 | end 22 | 23 | --- @generic T 24 | --- @param ... T[] 25 | --- @return T[] 26 | M.merge_lists = function(...) 27 | local merged = {} 28 | for _, arg in ipairs({ ... }) do 29 | for _, v in ipairs(arg) do 30 | table.insert(merged, v) 31 | end 32 | end 33 | return merged 34 | end 35 | 36 | --- @generic T 37 | --- @param language string 38 | --- @param snippets T[] 39 | --- @return T[] 40 | M.extend_language = function(language, snippets) 41 | local combined_snippets = require(M.extension_name .. ".luasnippets." .. language) 42 | return M.merge_lists(combined_snippets, snippets) 43 | end 44 | 45 | --- @param start number 46 | --- @return unknown 47 | M.current_filename = function(start) 48 | local ls = require("luasnip") 49 | return ls.dynamic_node(start, function() 50 | return ls.snippet_node(nil, { ls.insert_node(1, vim.fn.expand("%:t:r")) }) 51 | end) 52 | end 53 | 54 | --- @param start number 55 | --- @return unknown 56 | M.current_folder = function(start) 57 | local ls = require("luasnip") 58 | return ls.dynamic_node(start, function() 59 | return ls.snippet_node(nil, { ls.insert_node(1, vim.fn.expand("%:p:h:t")) }) 60 | end) 61 | end 62 | 63 | --- @param s string 64 | --- @return string 65 | M.upper_first = function(s) 66 | return (s:gsub("^%l", string.upper)) 67 | end 68 | 69 | --- @param s string 70 | --- @return string 71 | M.lower_first = function(s) 72 | return (s:gsub("^%l", string.lower)) 73 | end 74 | 75 | --- @param s string 76 | --- @return string 77 | M.camel_case = function(s) 78 | return M.lower_first(s) .. s:sub(1):gsub("_(.)", string.upper) 79 | end 80 | 81 | --- @param s string 82 | --- @return string 83 | M.kebab_case = function(s) 84 | return s 85 | end 86 | 87 | --- @param i number 88 | --- @return unknown 89 | M.mirror_node = function(i) 90 | local ls = require("luasnip") 91 | return ls.function_node(function(args) 92 | return args[1][1] 93 | end, { i }) 94 | end 95 | 96 | --- @param index number The insert index 97 | --- @param reference_index number[] A list containing a single insert node index to mirror as the default value 98 | M.editable_mirror_node = function(index, reference_index) 99 | local ls = require("luasnip") 100 | 101 | return ls.dynamic_node(index, function(args) 102 | return ls.snippet_node(nil, { ls.insert_node(1, args[1]) }) 103 | end, reference_index) 104 | end 105 | 106 | --- @param i number 107 | --- @param typescript boolean 108 | M.typescript_tabstop = function(i, typescript) 109 | if not typescript then 110 | return {} 111 | end 112 | 113 | local ls = require("luasnip") 114 | 115 | return { 116 | ls.insert_node(i), 117 | } 118 | end 119 | 120 | --- @param i number 121 | --- @param typescript boolean 122 | M.typescript_mirror_node = function(i, typescript) 123 | if not typescript then 124 | return {} 125 | end 126 | 127 | return { 128 | M.mirror_node(i), 129 | } 130 | end 131 | 132 | --- @private 133 | --- @class vim-react-snippets.LuaSnipConfig 134 | --- @field trig string 135 | --- @field name? string 136 | --- @field desc? string 137 | 138 | --- @private 139 | --- @class vim-react-snippets.ConstSnippetOptions 140 | --- @field config LuaSnipConfig 141 | --- @field const_name string 142 | --- @field const_edit? number 143 | --- @field create_snippet fun(start: number): unknown[] 144 | 145 | --- @param opts vim-react-snippets.ConstSnippetOptions 146 | --- @return unknown[] 147 | M.const_snippet = function(opts) 148 | local const_name = opts.const_name 149 | local const_edit = opts.const_edit or 0 150 | local config = opts.config 151 | local create_snippet = opts.create_snippet 152 | 153 | local ls = require("luasnip") 154 | local conds = require("luasnip.extras.expand_conditions") 155 | local s = ls.snippet 156 | local t = ls.text_node 157 | local i = ls.insert_node 158 | 159 | local start = const_edit + 1 160 | local const_statement = { 161 | t("const "), 162 | t(" = "), 163 | } 164 | if const_edit > 0 then 165 | table.insert(const_statement, 2, i(const_edit, const_name)) 166 | else 167 | table.insert(const_statement, 2, t(const_name)) 168 | end 169 | 170 | return { 171 | s(config, M.merge_lists(const_statement, create_snippet(start)), { condition = conds.line_begin }), 172 | s(config, create_snippet(1)), 173 | } 174 | end 175 | 176 | return M 177 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/react-hooks.lua: -------------------------------------------------------------------------------- 1 | local util = require("vim-react-snippets.util") 2 | 3 | local ls = require("luasnip") 4 | 5 | local s = ls.snippet 6 | local sn = ls.snippet_node 7 | local d = ls.dynamic_node 8 | local t = ls.text_node 9 | local i = ls.insert_node 10 | local f = ls.function_node 11 | 12 | --- @param typescript boolean 13 | --- @return unknown[] 14 | local use_state = function(typescript) 15 | return { 16 | s( 17 | { 18 | trig = "useS", 19 | name = "useState", 20 | }, 21 | util.merge_lists( 22 | { 23 | t("const ["), 24 | i(1), 25 | t(", set"), 26 | f(function(args) 27 | return util.upper_first(args[1][1]:gsub("^is", "")) 28 | end, { 1 }), 29 | t("] = useState"), 30 | }, 31 | util.typescript_tabstop(3, typescript), 32 | { 33 | t("("), 34 | i(2), 35 | t(")"), 36 | } 37 | ) 38 | ), 39 | } 40 | end 41 | 42 | --- @param typescript boolean 43 | --- @return unknown[] 44 | local use_reducer = function(typescript) 45 | local state = { t("state") } 46 | local action = { t("action") } 47 | local return_type = {} 48 | local start = 3 49 | if typescript then 50 | start = 5 51 | table.insert(state, t(": ")) 52 | table.insert(state, i(3, "State")) 53 | table.insert(action, t(": ")) 54 | table.insert(action, i(4, "Action")) 55 | table.insert(return_type, t(": ")) 56 | table.insert(return_type, util.mirror_node(3)) 57 | end 58 | 59 | return { 60 | s( 61 | { 62 | trig = "useRed", 63 | name = "useReducer", 64 | }, 65 | util.merge_lists( 66 | { 67 | t("const ["), 68 | i(1, "state"), 69 | t(", "), 70 | i(2, "dispatch"), 71 | t("] = useReducer(function reducer("), 72 | }, 73 | state, 74 | { t(", ") }, 75 | action, 76 | { t(")") }, 77 | return_type, 78 | { 79 | t({ 80 | " {", 81 | "\tswitch (action.type) {", 82 | "\t\tdefault:", 83 | "\t\t\treturn state", 84 | "\t}", 85 | "", 86 | }), 87 | t("}, "), 88 | i(start, "initialState"), 89 | t(")"), 90 | } 91 | ) 92 | ), 93 | } 94 | end 95 | 96 | --- @param typescript boolean 97 | --- @return unknown[] 98 | local create_context_provider = function(typescript) 99 | local start = 1 100 | local interface = {} 101 | local context_typedef = {} 102 | if typescript then 103 | start = start + 2 104 | table.insert(interface, t({ "", "export interface " })) 105 | table.insert( 106 | interface, 107 | d(1, function() 108 | return sn(nil, { 109 | -- current filename without use prefix and Context suffix 110 | i(1, util.upper_first(vim.fn.expand("%:t:r"):gsub("^use", ""):gsub("Context$", ""))), 111 | i(2, "Context"), 112 | }) 113 | end) 114 | ) 115 | table.insert(interface, t({ " {", "", "}", "", "" })) 116 | 117 | table.insert(context_typedef, t("<")) 118 | table.insert(context_typedef, util.mirror_node(1)) 119 | table.insert(context_typedef, i(2, " | null")) 120 | table.insert(context_typedef, t(">")) 121 | end 122 | 123 | return { 124 | s( 125 | { 126 | trig = "ccp", 127 | name = "Create Context Provider", 128 | }, 129 | util.merge_lists( 130 | { 131 | t({ 'import { createContext, useContext } from "react"', "", "" }), 132 | }, 133 | interface, 134 | { 135 | t("const context = createContext"), 136 | }, 137 | context_typedef, 138 | { 139 | t("("), 140 | i(start, "null"), 141 | t(")"), 142 | t({ "", "const { Provider } = context", "", "" }), 143 | t("export function use"), 144 | }, 145 | util.typescript_mirror_node(1, typescript), 146 | { 147 | t("()"), 148 | t(typescript and ": " or ""), 149 | }, 150 | util.typescript_mirror_node(1, typescript), 151 | { 152 | t(typescript and " " or ""), 153 | t({ 154 | "{", 155 | "\tconst value = useContext(context)", 156 | "\tif (!value) {", 157 | "\t\t", 158 | }), 159 | t('throw new Error("'), 160 | util.mirror_node(1), 161 | t(' must be initialized.")'), 162 | t({ 163 | "", 164 | "\t}", 165 | "", 166 | "\treturn value", 167 | "}", 168 | }), 169 | } 170 | ) 171 | ), 172 | } 173 | end 174 | 175 | --- @param typescript boolean 176 | local react_hooks = function(typescript) 177 | return util.merge_lists( 178 | { 179 | s({ 180 | trig = "useE", 181 | name = "useEffect", 182 | }, { 183 | t({ "useEffect(() => {", "\t" }), 184 | i(0), 185 | t({ "", "}, [])" }), 186 | }), 187 | }, 188 | 189 | use_state(typescript), 190 | use_reducer(typescript), 191 | 192 | util.const_snippet({ 193 | config = { 194 | trig = "useR", 195 | name = "useRef", 196 | }, 197 | const_name = "ref", 198 | const_edit = 1, 199 | create_snippet = function(start) 200 | return util.merge_lists( 201 | { 202 | t("useRef"), 203 | }, 204 | util.typescript_tabstop(start + 1, typescript), 205 | { 206 | t("("), 207 | i(start, "null"), 208 | t(")"), 209 | } 210 | ) 211 | end, 212 | }), 213 | 214 | util.const_snippet({ 215 | config = { 216 | trig = "useC", 217 | name = "useContext", 218 | }, 219 | const_name = "context", 220 | const_edit = 1, 221 | create_snippet = function(start) 222 | return { 223 | t("useContext("), 224 | i(start), 225 | t(")"), 226 | } 227 | end, 228 | }), 229 | 230 | util.const_snippet({ 231 | config = { 232 | trig = "useCB", 233 | name = "useCallback", 234 | }, 235 | const_name = "callback", 236 | const_edit = 1, 237 | create_snippet = function(start) 238 | return { 239 | t("useCallback(("), 240 | i(start), 241 | t({ ") => {", "\t" }), 242 | i(start + 1), 243 | t({ "", "}, [])" }), 244 | } 245 | end, 246 | }), 247 | util.const_snippet({ 248 | config = { 249 | trig = "useM", 250 | name = "useMemo", 251 | }, 252 | const_name = "memoized", 253 | const_edit = 1, 254 | create_snippet = function(start) 255 | return { 256 | t({ "useMemo(() => ({", "\t" }), 257 | i(start), 258 | t({ "", "}), [])" }), 259 | } 260 | end, 261 | }), 262 | util.const_snippet({ 263 | config = { 264 | trig = "useMR", 265 | name = "useMemo Return", 266 | }, 267 | const_name = "memoized", 268 | const_edit = 1, 269 | create_snippet = function(start) 270 | return { 271 | t({ "useMemo(() => {", "\t" }), 272 | i(start), 273 | t({ "", "}, [])" }), 274 | } 275 | end, 276 | }), 277 | create_context_provider(typescript) 278 | ) 279 | end 280 | 281 | return react_hooks 282 | -------------------------------------------------------------------------------- /lua/vim-react-snippets/react-components.lua: -------------------------------------------------------------------------------- 1 | local util = require("vim-react-snippets.util") 2 | local config = require("vim-react-snippets.config") 3 | 4 | local ls = require("luasnip") 5 | 6 | local s = ls.snippet 7 | local t = ls.text_node 8 | local i = ls.insert_node 9 | 10 | --- @private 11 | --- @class vim-react-snippets.FunctionComponentOptions 12 | --- @field props boolean 13 | --- @field export boolean | "default" 14 | --- @field server boolean 15 | --- @field forward boolean 16 | --- @field typescript boolean 17 | 18 | --- @param opts vim-react-snippets.FunctionComponentOptions 19 | local react_imports = function(opts) 20 | if not opts.export then 21 | return {} 22 | end 23 | 24 | local parts = {} 25 | if opts.forward then 26 | table.insert(parts, "forwardRef") 27 | elseif opts.typescript then 28 | table.insert(parts, "type ReactElement") 29 | end 30 | 31 | if opts.props and opts.typescript then 32 | table.insert(parts, "type ReactNode") 33 | end 34 | 35 | local joined = table.concat(parts, ", ") 36 | if #joined < 1 then 37 | return {} 38 | end 39 | 40 | return { 41 | t({ "import { " .. joined .. ' } from "react"', "", "" }), 42 | } 43 | end 44 | 45 | --- @param opts vim-react-snippets.FunctionComponentOptions 46 | local component_props = function(opts) 47 | if not opts.typescript or (not opts.props and not opts.forward) then 48 | return {} 49 | end 50 | 51 | local export = opts.export ~= false 52 | 53 | return { 54 | t((export and "export " or "") .. "interface "), 55 | util.mirror_node(1), 56 | t({ "Props {", "\t" }), 57 | i(2), 58 | t({ "", "\tchildren: ReactNode", "}", "", "" }), 59 | } 60 | end 61 | 62 | --- @param opts vim-react-snippets.FunctionComponentOptions 63 | --- @return unknown[] 64 | local component_func = function(opts) 65 | local props = opts.props 66 | local server = opts.server 67 | local forward = opts.forward 68 | local typescript = opts.typescript 69 | 70 | --- @type unknown[] 71 | local parts = {} 72 | if server then 73 | table.insert(parts, t("async ")) 74 | end 75 | table.insert(parts, t("function ")) 76 | table.insert(parts, util.current_filename(1)) 77 | table.insert(parts, t("(")) 78 | 79 | if forward then 80 | table.insert(parts, t("props, ref) {")) 81 | elseif typescript then 82 | if props then 83 | table.insert(parts, t("props: " .. (config.readonly_props and "Readonly<" or ""))) 84 | table.insert(parts, util.mirror_node(1)) 85 | table.insert(parts, t("Props" .. (config.readonly_props and ">" or ""))) 86 | end 87 | table.insert(parts, t("): " .. (server and "Promise<" or "") .. "ReactElement" .. (server and ">" or "") .. " {")) 88 | elseif props then 89 | table.insert(parts, t("props) {")) 90 | else 91 | table.insert(parts, t(") {")) 92 | end 93 | table.insert(parts, t({ "", "" })) 94 | 95 | return parts 96 | end 97 | 98 | --- @param opts vim-react-snippets.FunctionComponentOptions 99 | local forward_ref = function(opts) 100 | if not opts.forward then 101 | return {} 102 | end 103 | 104 | local types = {} 105 | if opts.typescript then 106 | table.insert(types, t("")) 111 | end 112 | 113 | return util.merge_lists({ t("forwardRef") }, types, { t("(") }) 114 | end 115 | 116 | --- @param opts vim-react-snippets.FunctionComponentOptions 117 | local component_export_line = function(opts) 118 | local export = opts.export ~= false 119 | local default = opts.export == "default" 120 | local forward = opts.forward 121 | local maybe_const = (not default and forward and { 122 | t("const "), 123 | util.mirror_node(1), 124 | t(" = "), 125 | }) or {} 126 | 127 | return util.merge_lists({ 128 | t((export and "export " or "") .. (default and "default " or "")), 129 | }, maybe_const, forward_ref(opts), component_func(opts)) 130 | end 131 | 132 | --- @param opts vim-react-snippets.FunctionComponentOptions 133 | local component_body = function(opts) 134 | if opts.forward then 135 | return { 136 | t({ "\treturn (", "" }), 137 | t({ "\t\t
", "" }), 138 | i(0), 139 | t({ "\t\t
", "" }), 140 | t({ "\t)", "" }), 141 | } 142 | end 143 | 144 | return { 145 | i(0), 146 | t({ "\treturn <>", "" }), 147 | } 148 | end 149 | 150 | --- @param opts vim-react-snippets.FunctionComponentOptions 151 | local function_component = function(opts) 152 | local props = opts.props 153 | local export = opts.export ~= false 154 | local default = opts.export == "default" 155 | local forward = opts.forward 156 | 157 | local simple = not props and not forward 158 | local trig = (simple and "s" or "") 159 | .. (forward and "f" or "") 160 | .. "fc" 161 | .. (default and "d" or "") 162 | .. (export and "e" or "") 163 | 164 | local desc = (simple and "Simple " or "") 165 | .. (forward and "Forwarded" or "") 166 | .. "Function Component " 167 | .. (default and "Default " or "") 168 | .. (export and "Export" or "") 169 | return s( 170 | { 171 | trig = trig, 172 | desc = desc, 173 | }, 174 | util.merge_lists(react_imports(opts), component_props(opts), component_export_line(opts), component_body(opts), { 175 | t("}" .. (opts.forward and ")" or "")), 176 | }) 177 | ) 178 | end 179 | 180 | local server_component = function(opts) 181 | local props = opts.props 182 | local export = opts.export 183 | local default = export == "default" 184 | local simple = not props 185 | 186 | --- @type vim-react-snippets.FunctionComponentOptions 187 | local shared_opts = { 188 | props = props, 189 | export = export, 190 | server = true, 191 | forward = false, 192 | typescript = true, 193 | } 194 | local trig = (simple and "s" or "") .. "sc" .. (default and "d" or "") .. (export and "e" or "") 195 | local desc = (simple and "Simple " or "") 196 | .. "Server Component " 197 | .. (default and "Default " or "") 198 | .. (export and "Export" or "") 199 | return s( 200 | { 201 | trig = trig, 202 | desc = desc, 203 | }, 204 | util.merge_lists( 205 | react_imports(shared_opts), 206 | component_props(shared_opts), 207 | component_export_line(shared_opts), 208 | component_body(shared_opts), 209 | { t("}") } 210 | ) 211 | ) 212 | end 213 | 214 | --- @param typescript boolean 215 | local function_components = function(typescript) 216 | return { 217 | -- fc 218 | function_component({ 219 | props = true, 220 | export = false, 221 | server = false, 222 | forward = false, 223 | typescript = typescript, 224 | }), 225 | -- fce 226 | function_component({ 227 | props = true, 228 | export = true, 229 | server = false, 230 | forward = false, 231 | typescript = typescript, 232 | }), 233 | -- fcde 234 | function_component({ 235 | props = true, 236 | export = "default", 237 | server = false, 238 | forward = false, 239 | typescript = typescript, 240 | }), 241 | 242 | -- sfc 243 | function_component({ 244 | props = false, 245 | export = false, 246 | server = false, 247 | forward = false, 248 | typescript = typescript, 249 | }), 250 | -- sfce 251 | function_component({ 252 | props = false, 253 | export = true, 254 | server = false, 255 | forward = false, 256 | typescript = typescript, 257 | }), 258 | -- sfcde 259 | function_component({ 260 | props = false, 261 | export = "default", 262 | server = false, 263 | forward = false, 264 | typescript = typescript, 265 | }), 266 | 267 | -- ffc 268 | function_component({ 269 | props = true, 270 | export = false, 271 | server = false, 272 | forward = true, 273 | typescript = typescript, 274 | }), 275 | -- ffce 276 | function_component({ 277 | props = true, 278 | export = true, 279 | server = false, 280 | forward = true, 281 | typescript = typescript, 282 | }), 283 | -- ffcde 284 | function_component({ 285 | props = true, 286 | export = "default", 287 | server = false, 288 | forward = true, 289 | typescript = typescript, 290 | }), 291 | } 292 | end 293 | 294 | local server_components = function(typescript) 295 | if not typescript then 296 | return {} 297 | end 298 | 299 | return { 300 | -- scde 301 | server_component({ 302 | props = true, 303 | export = "default", 304 | }), 305 | -- sscde 306 | server_component({ 307 | props = false, 308 | export = "default", 309 | }), 310 | 311 | -- sce 312 | server_component({ 313 | props = true, 314 | export = true, 315 | }), 316 | -- ssce 317 | server_component({ 318 | props = false, 319 | export = true, 320 | }), 321 | } 322 | end 323 | 324 | --- @private 325 | --- @param typescript boolean 326 | local react_components = function(typescript) 327 | return util.merge_lists(function_components(typescript), server_components(typescript)) 328 | end 329 | 330 | return react_components 331 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /UltiSnips/javascript.snippets: -------------------------------------------------------------------------------- 1 | global !p 2 | def upperfirst(t): 3 | if len(t) < 2: 4 | return '' if len(t) < 1 else t.capitalize() 5 | return t[0].capitalize() + t[1:] 6 | def lowerdash(t): 7 | return '-'.join([ w.lower() for w in re.findall('[a-z]+|[A-Z]+[a-z]*', t) ]) 8 | endglobal 9 | 10 | # Normal javascript snippets 11 | # 12 | snippet rc "require package that converts from camelCase or PascalCase to kebab-cased" b 13 | const $1 = require('$2`!p snip.rv=lowerdash(t[1])`') 14 | endsnippet 15 | 16 | snippet rcn "const { nested } = require('package')" b 17 | const { $1 } = require('$2') 18 | endsnippet 19 | 20 | snippet imp "import package that converts from camelCase or PascalCase to kebab-cased" b 21 | import $1 from '$2`!p snip.rv=lowerdash(t[1])`' 22 | endsnippet 23 | 24 | snippet impf "import File from './File'" b 25 | import $1 from '${2:./}${3:$1}' 26 | endsnippet 27 | 28 | snippet impn "import { nested } from" b 29 | import { $1 } from '$2' 30 | endsnippet 31 | 32 | snippet impa "import * as Thing from" b 33 | import * as $1 from "$2`!p snip.rv=lowerdash(t[1])`" 34 | endsnippet 35 | 36 | snippet impp "import file without a declaration" b 37 | import '$1' 38 | endsnippet 39 | 40 | snippet icn "import classnames as cn" b 41 | import cn from 'classnames' 42 | endsnippet 43 | 44 | snippet icnb "import { cnb } from 'cnbuilder'" b 45 | import { cnb } from 'cnbuilder' 46 | endsnippet 47 | 48 | snippet ism "import scss module" b 49 | import ${2:styles} from './${1:`!v expand('%:t:r')`}.module.scss' 50 | endsnippet 51 | 52 | snippet exp "export { default } from './CurrentFolderName'" 53 | export { default } from './${1:`!v expand('%:p:h:t')`}' 54 | endsnippet 55 | 56 | snippet expf "export File from './File'" b 57 | export $1 from '${2:./}$1' 58 | endsnippet 59 | 60 | snippet expn "export nested" b 61 | export { $1 } from '$2' 62 | endsnippet 63 | 64 | snippet expd "export { default as File } from './File'" b 65 | export { default as $1 } from '${2:./}$1' 66 | endsnippet 67 | 68 | snippet expa "export * from" b 69 | export * from '$1' 70 | endsnippet 71 | 72 | snippet cl "console.log" w 73 | console.log($1) 74 | endsnippet 75 | 76 | snippet clv "console.log variable" w 77 | console.log('$1:', ${1:}) 78 | endsnippet 79 | 80 | snippet ce "console.error" w 81 | console.error($1) 82 | endsnippet 83 | 84 | snippet cev "console.error variable" w 85 | console.error('$1: ', ${1:}) 86 | endsnippet 87 | 88 | snippet cw "console.warn" w 89 | console.warn($1) 90 | endsnippet 91 | 92 | snippet cwv "console.warn variable" w 93 | console.warn('$1: ', ${1:}) 94 | endsnippet 95 | 96 | snippet ct "console.table" w 97 | console.table($1) 98 | endsnippet 99 | 100 | snippet cd "console.debug" w 101 | console.debug($1) 102 | endsnippet 103 | 104 | snippet cdv "console.debug variable" w 105 | console.debug('$1: ', ${1:}) 106 | endsnippet 107 | 108 | snippet dev "process.env.NODE_ENV !== 'production'" w 109 | process.env.NODE_ENV !== 'production' 110 | endsnippet 111 | 112 | snippet prod "process.env.NODE_ENV === 'production'" w 113 | process.env.NODE_ENV === 'production' 114 | endsnippet 115 | 116 | snippet noop "const noop = () => {}" b 117 | const noop = () => {} 118 | endsnippet 119 | 120 | # ========================================== 121 | # Test Snippets 122 | # 123 | snippet rtf "React Test File" b 124 | import { render } from '@testing-library/react' 125 | 126 | import ${1:`!v expand('%:t:r')`} from '../$1' 127 | 128 | describe('$1', () => { 129 | it('should $2', () => { 130 | $0 131 | }) 132 | }) 133 | endsnippet 134 | 135 | snippet srtf "Screen React Test File" b 136 | import { render, screen } from '@testing-library/react' 137 | 138 | import ${1:`!v expand('%:t:r')`} from '../$1' 139 | 140 | describe('$1', () => { 141 | it('should $2', () => { 142 | $0 143 | }) 144 | }) 145 | endsnippet 146 | 147 | snippet desc "describe a test" b 148 | describe('$1', () => { 149 | it('should $2', () => { 150 | $0 151 | }) 152 | }) 153 | endsnippet 154 | 155 | snippet it "create a test block" b 156 | it('should $1', () => { 157 | $0 158 | }) 159 | endsnippet 160 | 161 | snippet ita "create an async test block" b 162 | it('should $1', async () => { 163 | $0 164 | }) 165 | endsnippet 166 | 167 | snippet todo "it.todo()" b 168 | it.todo('should $1') 169 | endsnippet 170 | 171 | snippet es "expect(container).toMatchSnapshot()" b 172 | expect(${1:container}).toMatchSnapshot()$0 173 | endsnippet 174 | 175 | snippet gbr "getByRole" b 176 | const $1 = getByRole("${2:button}", { name: "$3" }) 177 | endsnippet 178 | 179 | snippet gbru "getByRole unnamed" b 180 | const $1 = getByRole("${2:progressbar}") 181 | endsnippet 182 | 183 | snippet fbr "findByRole" b 184 | const $1 = await findByRole("${2:button}", { name: "$3" }) 185 | endsnippet 186 | 187 | snippet fbru "findByRole" b 188 | const $1 = await findByRole("${2:progressbar}") 189 | endsnippet 190 | 191 | snippet sgbr "screen.getByRole" b 192 | const $1 = screen.getByRole("${2:button}", { name: "$3" }) 193 | endsnippet 194 | 195 | snippet sgbru "screen.getByRole unnamed" b 196 | const $1 = screen.getByRole("${2:progressbar}") 197 | endsnippet 198 | 199 | snippet sfbr "screen.findByRole" b 200 | const $1 = await screen.findByRole("${2:button}", { name: "$3" }) 201 | endsnippet 202 | 203 | snippet sfbru "screen.findByRole unnamed" b 204 | const $1 = await screen.findByRole("${2:progressbar}") 205 | endsnippet 206 | 207 | snippet sgbi "screen.getByTestId" b 208 | const $1 = screen.getByTestId("${2:$1}") 209 | endsnippet 210 | 211 | snippet sgbt "screen.getByText" b 212 | const $1 = screen.getByText("$2") 213 | endsnippet 214 | 215 | snippet wf "wait for" b 216 | await waitFor(() => { 217 | $0 218 | }) 219 | endsnippet 220 | 221 | snippet ett "expect to throw" b 222 | expect(() => $1).toThrow() 223 | endsnippet 224 | 225 | snippet entt "expect not to throw" b 226 | expect(() => $1).not.toThrow() 227 | endsnippet 228 | 229 | snippet enc "expect not called (toHaveBeenCalled)" b 230 | expect($1).not.toHaveBeenCalled() 231 | endsnippet 232 | 233 | snippet ecw "expect toHaveBeenCalledWith" b 234 | expect($1).toHaveBeenCalledWith($2) 235 | endsnippet 236 | 237 | snippet ect "expect toHaveBeenCalledTimes" b 238 | expect($1).toHaveBeenCalledTimes($2) 239 | endsnippet 240 | 241 | # ========================================== 242 | # "Inline" versions of test snippets 243 | # 244 | priority -1 245 | 246 | snippet gbr "getByRole" w 247 | getByRole("${1:button}", { name: "$2" }) 248 | endsnippet 249 | 250 | snippet gbru "getByRole unnamed" w 251 | getByRole("${1:progressbar}") 252 | endsnippet 253 | 254 | snippet fbr "findByRole" w 255 | getByRole("${1:button}", { name: "$2" }) 256 | endsnippet 257 | 258 | snippet fbru "findByRole unnamed" w 259 | findByRole("${1:progressbar}") 260 | endsnippet 261 | 262 | snippet sgbr "screen.getByRole" w 263 | screen.getByRole("${1:button}", { name: "$2" }) 264 | endsnippet 265 | 266 | snippet sgbru "screen.getByRole unnamed" w 267 | screen.getByRole("${1:progressbar}") 268 | endsnippet 269 | 270 | snippet sfbr "screen.findByRole" w 271 | screen.getByRole("${1:button}", { name: "$2" }) 272 | endsnippet 273 | 274 | snippet sfbru "screen.findByRole unnamed" w 275 | screen.getByRole("${1:progressbar}") 276 | endsnippet 277 | 278 | snippet sgbi "screen.getByTestId" w 279 | screen.getByTestId("$1") 280 | endsnippet 281 | 282 | snippet sgbt "screen.getByText" w 283 | screen.getByText("$1") 284 | endsnippet 285 | 286 | priority 0 287 | 288 | # ========================================== 289 | # React Snippets 290 | # 291 | snippet rce "React class exported" b 292 | import { Component } from 'react' 293 | 294 | export default class ${1:`!v expand('%:t:r')`} extends Component { 295 | constructor(props) { 296 | super(props) 297 | 298 | this.state = {} 299 | } 300 | 301 | render() { 302 | return null$0 303 | } 304 | } 305 | endsnippet 306 | 307 | snippet rcc "React class component" b 308 | class $1 extends Component { 309 | render() { 310 | return null$0 311 | } 312 | } 313 | endsnippet 314 | 315 | snippet rcon "React class constructor" b 316 | constructor(props) { 317 | super(props) 318 | 319 | this.state = ${1:{}}$0 320 | } 321 | endsnippet 322 | 323 | snippet fce "function component exported" b 324 | const ${1:`!v expand('%:t:r')`} = props => { 325 | return null$0 326 | } 327 | 328 | export default $1 329 | endsnippet 330 | 331 | snippet sfce "simple function component export (no props)" b 332 | const ${1:`!v expand('%:t:r')`} = () => { 333 | return null$0 334 | } 335 | 336 | export default $1 337 | endsnippet 338 | 339 | snippet ffce "forwarded function component export" b 340 | import { forwardRef } from 'react' 341 | 342 | const $1 = forwardRef(function ${1:`!v expand('%:t:r')`}(props, ref) { 343 | return ( 344 |
$0 345 |
346 | ) 347 | }) 348 | 349 | export default $1 350 | endsnippet 351 | 352 | snippet rcf "react class function" b 353 | $1 = (${2:e}) => { 354 | $3 355 | } 356 | endsnippet 357 | 358 | # ========================================== 359 | # Component Specs and Lifecycle Section 360 | # 361 | snippet sdp "static default props" b 362 | static defaultProps = { 363 | $1 364 | } 365 | endsnippet 366 | 367 | snippet cdp "component default props" b 368 | ${1:`!v expand('%:t:r')`}.defaultProps = { 369 | $2 370 | } 371 | endsnippet 372 | 373 | snippet spt "static prop types" b 374 | static propTypes = { 375 | ${1:className}: ${2:PropTypes.string},$0 376 | } 377 | endsnippet 378 | 379 | snippet cpt "component prop types" b 380 | ${1:`!v expand('%:t:r')`}.propTypes = { 381 | ${2:className}: ${3:PropTypes.string},$0 382 | } 383 | endsnippet 384 | 385 | snippet gds "get derived state from props" b 386 | static getDerivedStateFromProps(nextProps, prevState) { 387 | return ${1:null}$0 388 | } 389 | endsnippet 390 | 391 | snippet gde "get derived state from error" b 392 | static getDerivedStateFromError(error) { 393 | return ${1:null}$0 394 | } 395 | endsnippet 396 | 397 | snippet cdm "component did mount" b 398 | componentDidMount() { 399 | $1 400 | } 401 | endsnippet 402 | 403 | snippet scu "should component update" b 404 | shouldComponentUpdate(nextProps, nextState) { 405 | return ${1:true}$0 406 | } 407 | endsnippet 408 | 409 | snippet gsbu "get snapshot before update" b 410 | getSnapshotBeforeUpdate(prevProps, prevState) { 411 | return ${1:null}$0 412 | } 413 | endsnippet 414 | 415 | snippet cdu "component did update" b 416 | componentDidUpdate(prevProps, prevState${1:, snapshot}) { 417 | $0 418 | } 419 | endsnippet 420 | 421 | snippet cdc "component did catch" b 422 | componentDidCatch(error, info) { 423 | $0 424 | } 425 | endsnippet 426 | 427 | snippet cwum "component will unmount" b 428 | componentWillUnmount() { 429 | $1 430 | } 431 | endsnippet 432 | 433 | # ========================================== 434 | # Deprecated Lifecycle Section 435 | # 436 | snippet cwm "component will mount" b 437 | componentWillMount() { 438 | $1 439 | } 440 | endsnippet 441 | 442 | snippet cwrp "component will receive props" b 443 | componentWillReceiveProps(nextProps) { 444 | $1 445 | } 446 | endsnippet 447 | 448 | snippet cwu "component will update" b 449 | componentWillUpdate(nextProps, nextState) { 450 | $3 451 | } 452 | endsnippet 453 | 454 | 455 | # ========================================== 456 | # Hooks and effects 457 | # 458 | 459 | snippet useS "useState()" b 460 | const [$1, set`!p snip.rv=upperfirst(t[1])`] = useState($2) 461 | endsnippet 462 | 463 | snippet useE "useEffect()" b 464 | useEffect(() => { 465 | $0 466 | }, []) 467 | endsnippet 468 | 469 | snippet useEA "useEffect() async" b 470 | useEffect(() => { 471 | let cancelled = false 472 | 473 | ;(async function ${3:doWork}() { 474 | ${1:// async work here} 475 | if (cancelled) { 476 | return 477 | } 478 | 479 | $2 480 | })() 481 | 482 | return () => { 483 | cancelled = true 484 | } 485 | }, []) 486 | endsnippet 487 | 488 | snippet useC "useContext()" b 489 | const ${1:context} = useContext($2) 490 | endsnippet 491 | 492 | snippet useRed "useReducer(reducer, initialState, getInitialState)" b 493 | const [${1:state}, ${2:dispatch}] = useReducer(function reducer(state, action) { 494 | return state 495 | }, ${3:null}) 496 | endsnippet 497 | 498 | snippet useCB "useCallback(fn, inputs)" b 499 | const ${1:callback} = useCallback(($2) => { 500 | $0 501 | }, []) 502 | endsnippet 503 | 504 | snippet useM "useMemo" b 505 | const ${1:memoized} = useMemo(() => ({ 506 | $0 507 | }), []) 508 | endsnippet 509 | 510 | snippet useMR "useMemo return" b 511 | const ${1:memoized} = useMemo(() => { 512 | $0 513 | }, []) 514 | endsnippet 515 | 516 | snippet useR "useRef(defaultValue)" b 517 | const ${1:ref} = useRef(${2:null}) 518 | endsnippet 519 | 520 | snippet useI "useImperativeHandle(ref, createHandle, [inputs])" b 521 | useImperativeHandle(${1:ref}, () => ({ 522 | $0 523 | }), []) 524 | endsnippet 525 | 526 | snippet useL "useLayoutEffect()" b 527 | useLayoutEffect(() => { 528 | $0 529 | }, []) 530 | endsnippet 531 | 532 | snippet useDV "useDebugValue(value)" b 533 | useDebugValue(${1:null}) 534 | endsnippet 535 | 536 | snippet useT "useTransition" b 537 | const [${1:isPending}, ${2:startTransition}] = useTransition() 538 | endsnippet 539 | 540 | # ========================================== 541 | # "Inline" versions of hooks 542 | # 543 | 544 | priority -1 545 | snippet useC "useContext()" w 546 | useContext($1)$0 547 | endsnippet 548 | 549 | snippet useCB "useCallback(fn, inputs)" w 550 | useCallback(($1) => { 551 | $0 552 | }, []) 553 | endsnippet 554 | 555 | snippet useM "useMemo" w 556 | useMemo(() => ({ 557 | $0 558 | }), []) 559 | endsnippet 560 | 561 | snippet useMR "useMemo return" b 562 | useMemo(() => { 563 | $0 564 | }, []) 565 | endsnippet 566 | 567 | snippet useR "useRef(defaultValue)" w 568 | useRef(${1:null})$0 569 | endsnippet 570 | 571 | priority 0 572 | 573 | # ========================================== 574 | # Other useful react/redux snippets 575 | # 576 | 577 | snippet mc "mirrored const" 578 | const $1 = '$1' 579 | endsnippet 580 | 581 | snippet useD "useDispatch" b 582 | const dispatch = useDispatch() 583 | endsnippet 584 | 585 | snippet useSL "useSelector(selector)" b 586 | const ${1:value} = useSelector(${2:state => $3}) 587 | endsnippet 588 | 589 | priority -1 590 | snippet useD "useDispatch" b 591 | useDispatch() 592 | endsnippet 593 | 594 | snippet useSL "useSelector(state)" w 595 | useSelector((${1:state => $2})) 596 | endsnippet 597 | 598 | snippet useAS "useSelector(state)" w 599 | useAppSelector(${1:state => $2}) 600 | endsnippet 601 | 602 | priority 0 603 | 604 | # ========================================== 605 | # redux toolkit snippets 606 | # 607 | 608 | snippet cs "createSlice()" b 609 | const { actions, reducer } = createSlice({ 610 | name: "${1:`!v expand('%:t:r')`}", 611 | initialState: $2, 612 | reducers: { 613 | $0 614 | } 615 | }) 616 | endsnippet 617 | 618 | snippet ecs "export createSlice()" b 619 | import { createSlice } from "@reduxjs/toolkit" 620 | 621 | const { actions, reducer } = createSlice({ 622 | name: "${1:`!v expand('%:t:r')`}", 623 | initialState: $2, 624 | reducers: { 625 | $3: (state) => state, 626 | } 627 | }) 628 | 629 | export const { $3 } = actions 630 | 631 | export default reducer 632 | endsnippet 633 | 634 | snippet cpr "create prepare reducer" b 635 | $1: { 636 | reducer(state, action) { 637 | $0 638 | }, 639 | prepare($2) { 640 | return { payload: { $3 } } 641 | } 642 | } 643 | endsnippet 644 | 645 | snippet cat "create async thunk" b 646 | export const $1 = createAsyncThunk("$2$1", async ($3) => { 647 | $0 648 | }) 649 | endsnippet 650 | 651 | priority -1 652 | 653 | snippet cat "create async thunk (inline)" w 654 | createAsyncThunk("$1", async ($2) => { 655 | $0 656 | }) 657 | endsnippet 658 | 659 | priority 0 660 | 661 | # ========================================== 662 | # General Built-Ins 663 | # 664 | 665 | snippet /** "jsdoc comment" b 666 | /** 667 | * $0 668 | */ 669 | endsnippet 670 | 671 | # ========================================== 672 | # Prop Types Section 673 | # 674 | 675 | # Inline prop types 676 | snippet pt.a "PropTypes.array" w 677 | PropTypes.array 678 | endsnippet 679 | 680 | snippet pt.ar "PropTypes.array.isRequired" w 681 | PropTypes.array.isRequired 682 | endsnippet 683 | 684 | snippet pt.b "PropTypes.bool" w 685 | PropTypes.bool 686 | endsnippet 687 | 688 | snippet pt.br "PropTypes.bool.isRequired" w 689 | PropTypes.bool.isRequired 690 | endsnippet 691 | 692 | snippet pt.f "PropTypes.func" w 693 | PropTypes.func 694 | endsnippet 695 | 696 | snippet pt.fr "PropTypes.func.isRequired" w 697 | PropTypes.func.isRequired 698 | endsnippet 699 | 700 | snippet pt.nu "PropTypes.number" w 701 | PropTypes.number 702 | endsnippet 703 | 704 | snippet pt.nur "PropTypes.number.isRequired" w 705 | PropTypes.number.isRequired 706 | endsnippet 707 | 708 | snippet pt.o "PropTypes.object" w 709 | PropTypes.object 710 | endsnippet 711 | 712 | snippet pt.or "PropTypes.object.isRequired" w 713 | PropTypes.object.isRequired 714 | endsnippet 715 | 716 | snippet pt.s "PropTyes.string" w 717 | PropTypes.string 718 | endsnippet 719 | 720 | snippet pt.sr "PropTyes.string.isRequired" w 721 | PropTypes.string.isRequired 722 | endsnippet 723 | 724 | snippet pt.no "PropTypes.node" w 725 | PropTypes.node 726 | endsnippet 727 | 728 | snippet pt.nor "PropTypes.node.isRequired" w 729 | PropTypes.node.isRequired 730 | endsnippet 731 | 732 | snippet pt.e "PropTypes.element" w 733 | PropTypes.element 734 | endsnippet 735 | 736 | snippet pt.er "PropTypes.element.isRequired" w 737 | PropTypes.element.isRequired 738 | endsnippet 739 | 740 | snippet pt.ao "PropTypes.arrayOf()" w 741 | PropTypes.arrayOf(${1:PropTypes.string}) 742 | endsnippet 743 | 744 | snippet pt.aor "PropTypes.arrayOf().isRequired" w 745 | PropTypes.arrayOf(${1:PropTypes.string}).isRequired 746 | endsnippet 747 | 748 | snippet pt.io "PropTypes.instanceOf()" w 749 | PropTypes.instanceOf(${1:PropTypes.string}) 750 | endsnippet 751 | 752 | snippet pt.ior "PropTypes.instanceOf().isRequired" w 753 | PropTypes.instanceOf(${1:PropTypes.string}).isRequired 754 | endsnippet 755 | 756 | snippet pt.oo "PropTypes.objectOf()" w 757 | PropTypes.objectOf(${1:PropTypes.string}) 758 | endsnippet 759 | 760 | snippet pt.oor "PropTypes.objectOf().isRequired" w 761 | PropTypes.objectOf(${1:PropTypes.string}).isRequired 762 | endsnippet 763 | 764 | snippet pt.one "PropTypes.oneOf([])" w 765 | PropTypes.oneOf(['$1'$2]) 766 | endsnippet 767 | 768 | snippet pt.oner "PropTypes.oneOf([]).isRequired" w 769 | PropTypes.oneOf(['$1'$2]).isRequired 770 | endsnippet 771 | 772 | snippet pt.onet "PropTYpes.oneOfType([])" w 773 | PropTypes.oneOfType([ 774 | $1 775 | ]) 776 | endsnippet 777 | 778 | snippet pt.onetr "PropTYpes.oneOfType([]).isRequired" w 779 | PropTypes.oneOfType([ 780 | $1 781 | ]).isRequired 782 | endsnippet 783 | 784 | snippet pt.sh "PropTypes.shape" w 785 | PropTypes.shape({ 786 | $1 787 | }) 788 | endsnippet 789 | 790 | snippet pt.shr "PropTypes.shape.isRequired" w 791 | PropTypes.shape({ 792 | $1 793 | }).isRequired 794 | endsnippet -------------------------------------------------------------------------------- /UltiSnips/typescript.snippets: -------------------------------------------------------------------------------- 1 | global !p 2 | def upperfirst(t): 3 | if len(t) < 2: 4 | return '' if len(t) < 1 else t.capitalize() 5 | return t[0].capitalize() + t[1:] 6 | def lowerdash(t): 7 | return '-'.join([ w.lower() for w in re.findall('[a-z]+|[A-Z]+[a-z]*', t) ]) 8 | endglobal 9 | 10 | # Normal javascript snippets 11 | # 12 | snippet rc "require package that converts from camelCase or PascalCase to kebab-cased" b 13 | const $1 = require("$2`!p snip.rv=lowerdash(t[1])`") 14 | endsnippet 15 | 16 | snippet rcn "const { nested } = require('package')" b 17 | const { $1 } = require("$2") 18 | endsnippet 19 | 20 | snippet imp "import package that converts from camelCase or PascalCase to kebab-cased" b 21 | import $1 from "$2`!p snip.rv=lowerdash(t[1])`" 22 | endsnippet 23 | 24 | snippet impf "import File from './File'" b 25 | import $1 from "${2:./}${3:$1}" 26 | endsnippet 27 | 28 | snippet impn "import { nested } from" b 29 | import { $1 } from "$2" 30 | endsnippet 31 | 32 | snippet impa "import all from a package that converts from camelCase or PascalCase to kebab-cased" b 33 | import * as $1 from "$2`!p snip.rv=lowerdash(t[1])`" 34 | endsnippet 35 | 36 | snippet impp "import file without a declaration" b 37 | import "$1" 38 | endsnippet 39 | 40 | snippet icn "import classnames as cn" b 41 | import cn from "classnames" 42 | endsnippet 43 | 44 | snippet icnb "import { cnb } from 'cnbuilder'" b 45 | import { cnb } from "cnbuilder" 46 | endsnippet 47 | 48 | snippet ism "import scss module" b 49 | import ${2:styles} from "./${1:`!v expand('%:t:r')`}.module.scss" 50 | endsnippet 51 | 52 | snippet exp "export { default } from './CurrentFolderName'" 53 | export { default } from "./${1:`!v expand('%:p:h:t')`}" 54 | endsnippet 55 | 56 | snippet expf "export File from './File'" b 57 | export $1 from "${2:./}$1" 58 | endsnippet 59 | 60 | snippet expn "export nested" b 61 | export { $1 } from "$2" 62 | endsnippet 63 | 64 | snippet expd "export { default as File } from './File'" b 65 | export { default as $1 } from "${2:./}$1" 66 | endsnippet 67 | 68 | snippet expa "export * from" b 69 | export * from "$1" 70 | endsnippet 71 | 72 | snippet cl "console.log" w 73 | console.log($1) 74 | endsnippet 75 | 76 | snippet clv "console.log variable" w 77 | console.log("$1:", ${1:}) 78 | endsnippet 79 | 80 | snippet ce "console.error" w 81 | console.error($1) 82 | endsnippet 83 | 84 | snippet cev "console.error variable" w 85 | console.error("$1: ", ${1:}) 86 | endsnippet 87 | 88 | snippet cw "console.warn" w 89 | console.warn($1) 90 | endsnippet 91 | 92 | snippet cwv "console.warn variable" w 93 | console.warn("$1: ", ${1:}) 94 | endsnippet 95 | 96 | snippet ct "console.table" w 97 | console.table($1) 98 | endsnippet 99 | 100 | snippet cd "console.debug" w 101 | console.debug($1) 102 | endsnippet 103 | 104 | snippet cdv "console.debug variable" w 105 | console.debug("$1: ", ${1:}) 106 | endsnippet 107 | 108 | snippet dev "process.env.NODE_ENV !== 'production'" w 109 | process.env.NODE_ENV !== "production" 110 | endsnippet 111 | 112 | snippet prod "process.env.NODE_ENV === 'production'" w 113 | process.env.NODE_ENV === "production" 114 | endsnippet 115 | 116 | snippet noop "const noop = () => {}" b 117 | const noop = (): void => { 118 | // do nothing 119 | } 120 | endsnippet 121 | 122 | # ========================================== 123 | # Test Snippets 124 | # 125 | snippet rtf "React Test File" b 126 | import { render } from "@testing-library/react" 127 | 128 | import { ${1:`!v expand('%:t:r')`} } from "../$1" 129 | 130 | describe("$1", () => { 131 | it("should $2", () => { 132 | $0 133 | }) 134 | }) 135 | endsnippet 136 | 137 | snippet srtf "Screen React Test File" b 138 | import { render, screen } from "@testing-library/react" 139 | 140 | import { ${1:`!v expand('%:t:r')`} } from "../$1" 141 | 142 | describe("$1", () => { 143 | it("should $2", () => { 144 | $0 145 | }) 146 | }) 147 | endsnippet 148 | 149 | snippet desc "describe a test" b 150 | describe("$1", () => { 151 | it("should $2", () => { 152 | $0 153 | }) 154 | }) 155 | endsnippet 156 | 157 | snippet it "create a test block" b 158 | it("should $1", () => { 159 | $0 160 | }) 161 | endsnippet 162 | 163 | snippet ita "create an async test block" b 164 | it('should $1', async () => { 165 | $0 166 | }) 167 | endsnippet 168 | 169 | snippet todo "it.todo()" b 170 | it.todo("should $1") 171 | endsnippet 172 | 173 | snippet es "expect(container).toMatchSnapshot()" b 174 | expect(${1:container}).toMatchSnapshot()$0 175 | endsnippet 176 | 177 | snippet gbr "getByRole" b 178 | const $1 = getByRole("${2:button}", { name: "$3" }) 179 | endsnippet 180 | 181 | snippet gbru "getByRole unnamed" b 182 | const $1 = getByRole("${2:progressbar}") 183 | endsnippet 184 | 185 | snippet fbr "findByRole" b 186 | const $1 = await findByRole("${2:button}", { name: "$3" }) 187 | endsnippet 188 | 189 | snippet fbru "findByRole unnamed" b 190 | const $1 = await findByRole("${2:progressbar}") 191 | endsnippet 192 | 193 | snippet sgbr "screen.getByRole" b 194 | const $1 = screen.getByRole("${2:button}", { name: "$3" }) 195 | endsnippet 196 | 197 | snippet sgbru "screen.getByRole unnamed" b 198 | const $1 = screen.getByRole("${2:progressbar}") 199 | endsnippet 200 | 201 | snippet sfbr "screen.findByRole" b 202 | const $1 = await screen.findByRole("${2:button}", { name: "$3" }) 203 | endsnippet 204 | 205 | snippet sfbru "screen.findByRole unnamed" b 206 | const $1 = await screen.findByRole("${2:progressbar}") 207 | endsnippet 208 | 209 | snippet sgbi "screen.getByTestId" b 210 | const $1 = screen.getByTestId("${2:$1}") 211 | endsnippet 212 | 213 | snippet sgbt "screen.getByText" b 214 | const $1 = screen.getByText("$2") 215 | endsnippet 216 | 217 | snippet wf "wait for" b 218 | await waitFor(() => { 219 | $0 220 | }) 221 | endsnippet 222 | 223 | snippet ett "expect to throw" b 224 | expect(() => $1).toThrow() 225 | endsnippet 226 | 227 | snippet entt "expect not to throw" b 228 | expect(() => $1).not.toThrow() 229 | endsnippet 230 | 231 | snippet enc "expect not called (toHaveBeenCalled)" b 232 | expect($1).not.toHaveBeenCalled() 233 | endsnippet 234 | 235 | snippet ecw "expect called with (toHaveBeenCalledWith)" b 236 | expect($1).toHaveBeenCalledWith($2) 237 | endsnippet 238 | 239 | snippet ect "expect called times (toHaveBeenCalledTimes)" b 240 | expect($1).toHaveBeenCalledTimes($2) 241 | endsnippet 242 | 243 | # ========================================== 244 | # "Inline" versions of test snippets 245 | # 246 | priority -1 247 | 248 | snippet gbr "getByRole" w 249 | getByRole("${1:button}", { name: "$2" }) 250 | endsnippet 251 | 252 | snippet gbru "getByRole unnamed" w 253 | getByRole("${1:progressbar}") 254 | endsnippet 255 | 256 | snippet fbr "findByRole" w 257 | findByRole("${1:button}", { name: "$2" }) 258 | endsnippet 259 | 260 | snippet fbr "findByRole unnamed" w 261 | findByRole("${1:progressbar}") 262 | endsnippet 263 | 264 | snippet sgbr "screen.getByRole" w 265 | screen.getByRole("${1:button}", { name: "$2" }) 266 | endsnippet 267 | 268 | snippet sgbru "screen.getByRole unnamed" w 269 | screen.getByRole("${1:progressbar}") 270 | endsnippet 271 | 272 | snippet sfbr "screen.findByRole" w 273 | screen.findByRole("${1:button}", { name: "$2" }) 274 | endsnippet 275 | 276 | snippet sfbru "screen.findByRole unnamed" w 277 | screen.findByRole("${1:progressbar}") 278 | endsnippet 279 | 280 | snippet sgbi "screen.getByTestId" w 281 | screen.getByTestId("$1") 282 | endsnippet 283 | 284 | snippet sgbt "screen.getByText" w 285 | screen.getByText("$1") 286 | endsnippet 287 | 288 | priority 0 289 | 290 | # ========================================== 291 | # React Snippets 292 | # 293 | snippet rce "react class exported" b 294 | import { Component } from "react" 295 | 296 | export default class ${1:`!v expand('%:t:r')`} extends Component { 297 | public render() { 298 | return ${2:null}$0 299 | } 300 | } 301 | endsnippet 302 | 303 | snippet rcep "react class exported (with prop interface)" b 304 | import { Component } from "react" 305 | 306 | export interface $1Props { 307 | } 308 | 309 | export default class ${1:`!v expand('%:t:r')`} extends Component<$1Props> { 310 | public render() { 311 | return ${2:null}$0 312 | } 313 | } 314 | endsnippet 315 | 316 | snippet rceps "react class exported (with prop and state interfaces)" b 317 | import { Component } from "react" 318 | 319 | export interface $1Props { 320 | } 321 | 322 | export interface $1State { 323 | } 324 | 325 | export default class ${1:`!v expand('%:t:r')`} extends Component<$1Props, $1State> { 326 | constructor(props: $1Props) { 327 | super(props) 328 | 329 | this.state = {} 330 | } 331 | 332 | public render() { 333 | return ${2:null}$0 334 | } 335 | } 336 | endsnippet 337 | 338 | snippet rcc "React class component" b 339 | class $1 extends Component { 340 | public render() { 341 | return null$0 342 | } 343 | } 344 | endsnippet 345 | 346 | snippet rcon "React class constructor" b 347 | constructor(props: ${1:`!v expand('%:t:r')`}Props) { 348 | super(props) 349 | 350 | this.state = ${1:{}}$0 351 | } 352 | endsnippet 353 | 354 | snippet fce "function component export (with prop interface)" b 355 | import { type ReactElement } from "react" 356 | 357 | export interface $1Props { 358 | $3 359 | } 360 | 361 | export function ${1:`!v expand('%:t:r')`}(${2:props}: $1Props): ReactElement { 362 | return ${4:<>}$0 363 | } 364 | endsnippet 365 | 366 | snippet fcde "function component default export (with prop interface)" b 367 | import { type ReactElement } from "react" 368 | 369 | export interface $1Props { 370 | $3 371 | } 372 | 373 | export default function ${1:`!v expand('%:t:r')`}(${2:props}: $1Props): ReactElement { 374 | return ${4:<>}$0 375 | } 376 | endsnippet 377 | 378 | snippet sfce "simple function component export (no prop interface)" b 379 | import { type ReactElement } from "react" 380 | 381 | export function ${1:`!v expand('%:t:r')`}(): ReactElement { 382 | return ${2:<>}$0 383 | } 384 | endsnippet 385 | 386 | snippet sfcde "simple function component default export (no prop interface)" b 387 | import { type ReactElement } from "react" 388 | 389 | export default function ${1:`!v expand('%:t:r')`}(): ReactElement { 390 | return ${2:<>}$0 391 | } 392 | endsnippet 393 | 394 | snippet ffce "forwarded function component export" 395 | import { forwardRef } from "react" 396 | 397 | export interface ${1:`!v expand('%:t:r')`}Props { 398 | $3 399 | } 400 | 401 | export const $1 = forwardRef(function $1(props, ref) { 402 | return ( 403 |
$0 404 |
405 | ) 406 | }) 407 | endsnippet 408 | 409 | snippet ffcde "forwarded function component default export" 410 | import { forwardRef } from "react" 411 | 412 | export interface ${1:`!v expand('%:t:r')`}Props { 413 | $3 414 | } 415 | 416 | export default forwardRef(function $1(props, ref) { 417 | return ( 418 |
$0 419 |
420 | ) 421 | }) 422 | endsnippet 423 | 424 | snippet rcf "react class function" b 425 | $1 = (${2:event}) => { 426 | $3 427 | } 428 | endsnippet 429 | 430 | # ========================================== 431 | # Component Specs and Lifecycle Section 432 | # 433 | snippet sdp "static default props" b 434 | public static defaultProps = { 435 | $2 436 | } 437 | endsnippet 438 | 439 | snippet sdpt "static default props (typed)" b 440 | public static defaultProps: DefaultProps = { 441 | $2 442 | } 443 | endsnippet 444 | 445 | snippet cdp "component default props" b 446 | ${1:`!v expand('%:t:r')`}.defaultProps = { 447 | $2 448 | } 449 | endsnippet 450 | 451 | snippet cdpt "component default props (typed)" b 452 | const defaultProps: $1DefaultProps = { 453 | $2 454 | } 455 | 456 | ${3:`!v expand('%:t:r')`}.defaultProps = defaultProps 457 | endsnippet 458 | 459 | snippet spt "static prop types" b 460 | public static propTypes = { 461 | ${1:className}: ${2:PropTypes.string},$0 462 | } 463 | endsnippet 464 | 465 | snippet cpt "component prop types" b 466 | ${1:`!v expand('%:t:r')`}.propTypes = { 467 | ${2:className}: ${3:PropTypes.string},$0 468 | } 469 | endsnippet 470 | 471 | snippet gds "get derived state from props" b 472 | public static getDerivedStateFromProps(nextProps: ${1:`!v expand('%:t:r')`}Props, prevState: $1State) { 473 | return ${2:null}$0 474 | } 475 | endsnippet 476 | 477 | snippet gde "get derived state from props" b 478 | public static getDerivedStateFromError(error: Error) { 479 | return ${1:null}$0 480 | } 481 | endsnippet 482 | 483 | snippet cdm "component did mount" b 484 | public componentDidMount() { 485 | $1 486 | } 487 | endsnippet 488 | 489 | snippet scu "should component update" b 490 | public shouldComponentUpdate(nextProps: ${1:`!v expand('%:t:r')`}Props, nextState: $1State) { 491 | return ${1:true}$0 492 | } 493 | endsnippet 494 | 495 | snippet gsbu "get snapshot before update" b 496 | public getSnapshotBeforeUpdate(prevProps: ${1:`!v expand('%:t:r')`}Props, prevState: $1State) { 497 | return ${2:null}$0 498 | } 499 | endsnippet 500 | 501 | snippet cdu "component did update" b 502 | public componentDidUpdate(prevProps: ${1:`!v expand('%:t:r')`}Props, prevState: $1State, ${2:snapshot: any}) { 503 | $3 504 | } 505 | endsnippet 506 | 507 | snippet cdc "component did catch" b 508 | public componentDidCatch(error: Error, info: ErrorInfo) { 509 | $1 510 | } 511 | endsnippet 512 | 513 | snippet cwum "component will unmount" b 514 | public componentWillUnmount() { 515 | $1 516 | } 517 | endsnippet 518 | 519 | snippet me "MouseEvent" w 520 | ${1:event: }MouseEvent$0 521 | endsnippet 522 | 523 | snippet te "TouchEvent" w 524 | ${1:event: }TouchEvent$0 525 | endsnippet 526 | 527 | snippet ke "KeyboardEvent" w 528 | ${1:event: }KeyboardEvent$0 529 | endsnippet 530 | 531 | snippet che "ChangeEvent" w 532 | ${1:event: }ChangeEvent$0 533 | endsnippet 534 | 535 | snippet fe "FocusEvent" w 536 | ${1:event: }FocusEvent$0 537 | endsnippet 538 | 539 | snippet foe "FormEvent" w 540 | ${1:event: }FormEvent$0 541 | endsnippet 542 | 543 | snippet meh "MouseEventHandler" w 544 | MouseEventHandler$0 545 | endsnippet 546 | 547 | snippet teh "TouchEventHandler" w 548 | TouchEventHandler$0 549 | endsnippet 550 | 551 | snippet keh "KeyboardEventHandler" w 552 | KeyboardEventHandler$0 553 | endsnippet 554 | 555 | snippet cheh "ChangeEventHandler" w 556 | ChangeEventHandler$0 557 | endsnippet 558 | 559 | snippet feh "FocusEventHandler" w 560 | FocusEventHandler$0 561 | endsnippet 562 | 563 | snippet foeh "FormEventHandler" w 564 | FormEventHandler$0 565 | endsnippet 566 | 567 | 568 | # ========================================== 569 | # Hooks and effects 570 | # 571 | 572 | snippet useS "useState()" b 573 | const [$1, set`!p snip.rv=upperfirst(t[1])`] = useState$3($2)$0 574 | endsnippet 575 | 576 | snippet useE "useEffect()" b 577 | useEffect(() => { 578 | $0 579 | }, []) 580 | endsnippet 581 | 582 | snippet useEA "useEffect() async" b 583 | useEffect(() => { 584 | let cancelled = false 585 | 586 | ;(async function ${3:doWork}(): Promise<${4:void}> { 587 | ${1:// async work here} 588 | if (cancelled) { 589 | return 590 | } 591 | 592 | $2 593 | })() 594 | 595 | return () => { 596 | cancelled = true 597 | } 598 | }, []) 599 | endsnippet 600 | 601 | snippet useC "useContext()" b 602 | const ${1:context} = useContext($2)$0 603 | endsnippet 604 | 605 | snippet useRed "useReducer(reducer, initialState, getInitialState)" b 606 | const [${4:state}, ${5:dispatch}] = useReducer(function reducer(state: $1, action: $2): $1 { 607 | return state 608 | }, ${3:null}) 609 | endsnippet 610 | 611 | snippet useCB "useCallback(fn, inputs)" b 612 | const ${1:callback} = useCallback(($2) => { 613 | $0 614 | }, []) 615 | endsnippet 616 | 617 | snippet useM "useMemo" b 618 | const ${1:memoized} = useMemo(() => ({ 619 | $0 620 | }), []) 621 | endsnippet 622 | 623 | snippet useMR "useMemo return)" b 624 | const ${1:memoized} = useMemo(() => { 625 | $0 626 | }, []) 627 | endsnippet 628 | 629 | snippet useR "useRef(defaultValue)" b 630 | const ${1:ref} = useRef$3(${2:null})$0 631 | endsnippet 632 | 633 | snippet useI "useImperativeHandle(ref, createHandle, [inputs])" b 634 | useImperativeHandle(${1:ref}, () => ({ 635 | $0 636 | }), []) 637 | endsnippet 638 | 639 | snippet useL "useLayoutEffect()" b 640 | useLayoutEffect(() => { 641 | $0 642 | }, []) 643 | endsnippet 644 | 645 | snippet useDV "useDebugValue(value)" b 646 | useDebugValue(${1:null}) 647 | endsnippet 648 | 649 | snippet useT "useTransition" b 650 | const [${1:isPending}, ${2:startTransition}] = useTransition() 651 | endsnippet 652 | 653 | # ========================================== 654 | # "Inline" versions of hooks 655 | # 656 | 657 | priority -1 658 | snippet useC "useContext()" w 659 | useContext($1)$0 660 | endsnippet 661 | 662 | snippet useCB "useCallback(fn, inputs)" w 663 | useCallback(($1) => { 664 | $0 665 | }, []) 666 | endsnippet 667 | 668 | snippet useM "useMemo" w 669 | useMemo(() => ({ 670 | $0 671 | }), []) 672 | endsnippet 673 | 674 | snippet useMR "useMemo return" w 675 | useMemo(() => { 676 | $0 677 | }, []) 678 | endsnippet 679 | 680 | snippet useR "useRef(defaultValue)" w 681 | useRef$2(${1:null})$0 682 | endsnippet 683 | 684 | priority 0 685 | 686 | 687 | # ========================================== 688 | # Other useful react/redux snippets 689 | # 690 | 691 | snippet mc "mirrored const" 692 | const $1 = '$1' 693 | endsnippet 694 | 695 | snippet useD "useAppDispatch()" b 696 | const dispatch = useAppDispatch() 697 | endsnippet 698 | 699 | snippet useDS "useDispatch()" b 700 | const dispatch${1:: AppDispatch} = useDispatch() 701 | endsnippet 702 | 703 | snippet useSL "useSelector(selector)" b 704 | const ${1:value} = useSelector((${2:state${3:: AppState} => $4})) 705 | endsnippet 706 | 707 | snippet useAS "useAppSelector(selector)" b 708 | const ${1:value} = useAppSelector(${2:state => $3}) 709 | endsnippet 710 | 711 | priority -1 712 | snippet useD "useDispatch()" w 713 | useAppDispatch() 714 | endsnippet 715 | 716 | snippet useSL "useSelector(state)" w 717 | useSelector((${1:state${2:: AppState} => $3})) 718 | endsnippet 719 | 720 | snippet useAS "useAppSelector(state)" w 721 | useAppSelector(${1:state => $2}) 722 | endsnippet 723 | 724 | priority 0 725 | 726 | # ========================================== 727 | # redux toolkit snippets 728 | # 729 | 730 | snippet cs "createSlice()" b 731 | const { actions, reducer } = createSlice({ 732 | name: "${1:`!v expand('%:t:r')`}", 733 | initialState: $2, 734 | reducers: { 735 | $0 736 | } 737 | }) 738 | endsnippet 739 | 740 | snippet ecs "export createSlice()" b 741 | import { createSlice } from "@reduxjs/toolkit" 742 | 743 | const { actions, reducer } = createSlice({ 744 | name: "${1:`!v expand('%:t:r')`}", 745 | initialState: $2, 746 | reducers: { 747 | $3: (state) => state, 748 | } 749 | }) 750 | 751 | export const { $3 } = actions 752 | 753 | export default reducer 754 | endsnippet 755 | 756 | snippet cpr "create prepare reducer" b 757 | $1: { 758 | reducer(state, action: ${2:PayloadAction}<$3>) { 759 | $0 760 | }, 761 | prepare($4) { 762 | return { payload: { $5 } } 763 | } 764 | } 765 | endsnippet 766 | 767 | snippet cat "create async thunk" b 768 | export const $1 = createAsyncThunk("$2$1", async ($3) => { 769 | $0 770 | }) 771 | endsnippet 772 | 773 | priority -1 774 | 775 | snippet cat "create async thunk (inline)" w 776 | createAsyncThunk("$1", async ($2) => { 777 | $0 778 | }) 779 | endsnippet 780 | 781 | priority 0 782 | 783 | # ========================================== 784 | # General Built-Ins 785 | # 786 | 787 | snippet intf "create interface for file" b 788 | export interface ${1:`!v expand('%:t:r')`}$2 { 789 | $0 790 | } 791 | endsnippet 792 | 793 | snippet re "reduce" w 794 | reduce<$1>((${2:result}, ${3:value}) => { 795 | $0 796 | 797 | return $2 798 | }, ${4:{}}) 799 | endsnippet 800 | 801 | snippet /** "jsdoc comment" b 802 | /** 803 | * $0 804 | */ 805 | endsnippet 806 | 807 | # ========================================== 808 | # Prop Types Section (only for legacy) 809 | # 810 | 811 | # Inline prop types 812 | snippet pt.a "PropTypes.array" w 813 | PropTypes.array 814 | endsnippet 815 | 816 | snippet pt.ar "PropTypes.array.isRequired" w 817 | PropTypes.array.isRequired 818 | endsnippet 819 | 820 | snippet pt.b "PropTypes.bool" w 821 | PropTypes.bool 822 | endsnippet 823 | 824 | snippet pt.br "PropTypes.bool.isRequired" w 825 | PropTypes.bool.isRequired 826 | endsnippet 827 | 828 | snippet pt.f "PropTypes.func" w 829 | PropTypes.func 830 | endsnippet 831 | 832 | snippet pt.fr "PropTypes.func.isRequired" w 833 | PropTypes.func.isRequired 834 | endsnippet 835 | 836 | snippet pt.nu "PropTypes.number" w 837 | PropTypes.number 838 | endsnippet 839 | 840 | snippet pt.nur "PropTypes.number.isRequired" w 841 | PropTypes.number.isRequired 842 | endsnippet 843 | 844 | snippet pt.o "PropTypes.object" w 845 | PropTypes.object 846 | endsnippet 847 | 848 | snippet pt.or "PropTypes.object.isRequired" w 849 | PropTypes.object.isRequired 850 | endsnippet 851 | 852 | snippet pt.s "PropTyes.string" w 853 | PropTypes.string 854 | endsnippet 855 | 856 | snippet pt.sr "PropTyes.string.isRequired" w 857 | PropTypes.string.isRequired 858 | endsnippet 859 | 860 | snippet pt.no "PropTypes.node" w 861 | PropTypes.node 862 | endsnippet 863 | 864 | snippet pt.nor "PropTypes.node.isRequired" w 865 | PropTypes.node.isRequired 866 | endsnippet 867 | 868 | snippet pt.e "PropTypes.element" w 869 | PropTypes.element 870 | endsnippet 871 | 872 | snippet pt.er "PropTypes.element.isRequired" w 873 | PropTypes.element.isRequired 874 | endsnippet 875 | 876 | snippet pt.ao "PropTypes.arrayOf()" w 877 | PropTypes.arrayOf(${1:PropTypes.string}) 878 | endsnippet 879 | 880 | snippet pt.aor "PropTypes.arrayOf().isRequired" w 881 | PropTypes.arrayOf(${1:PropTypes.string}).isRequired 882 | endsnippet 883 | 884 | snippet pt.io "PropTypes.instanceOf()" w 885 | PropTypes.instanceOf(${1:PropTypes.string}) 886 | endsnippet 887 | 888 | snippet pt.ior "PropTypes.instanceOf().isRequired" w 889 | PropTypes.instanceOf(${1:PropTypes.string}).isRequired 890 | endsnippet 891 | 892 | snippet pt.oo "PropTypes.objectOf()" w 893 | PropTypes.objectOf(${1:PropTypes.string}) 894 | endsnippet 895 | 896 | snippet pt.oor "PropTypes.objectOf().isRequired" w 897 | PropTypes.objectOf(${1:PropTypes.string}).isRequired 898 | endsnippet 899 | 900 | snippet pt.one "PropTypes.oneOf([])" w 901 | PropTypes.oneOf(["$1"$2]) 902 | endsnippet 903 | 904 | snippet pt.oner "PropTypes.oneOf([]).isRequired" w 905 | PropTypes.oneOf(["$1"$2]).isRequired 906 | endsnippet 907 | 908 | snippet pt.onet "PropTYpes.oneOfType([])" w 909 | PropTypes.oneOfType([ 910 | $1 911 | ]) 912 | endsnippet 913 | 914 | snippet pt.onetr "PropTYpes.oneOfType([]).isRequired" w 915 | PropTypes.oneOfType([ 916 | $1 917 | ]).isRequired 918 | endsnippet 919 | 920 | snippet pt.sh "PropTypes.shape" w 921 | PropTypes.shape({ 922 | $1 923 | }) 924 | endsnippet 925 | 926 | snippet pt.shr "PropTypes.shape.isRequired" w 927 | PropTypes.shape({ 928 | $1 929 | }).isRequired 930 | endsnippet -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-react-snippets 2 | 3 | A collection of common Javascript and Typescript vim snippets for developing 4 | [React] applications. The snippets within this repo rely on [LuaSnip] or 5 | [UltiSnips] as the snippet provider. 6 | 7 | ## Typescript Example 8 | 9 | 10 | 11 | ## Previewing Snippets with [coc-snippets](https://github.com/neoclide/coc-snippets) 12 | 13 | 14 | 15 | ## Using Log Helpers 16 | 17 | 18 | 19 | ## Writing Tests 20 | 21 | 22 | 23 | ## Installation 24 | 25 | With [lazy.nvim] and [LuaSnip]: 26 | 27 | ```diff 28 | { 29 | "L3MON4D3/LuaSnip", 30 | version = "v2.*", 31 | dependencies = { 32 | + { "mlaursen/vim-react-snippets", opts = {} }, 33 | }, 34 | ``` 35 | 36 | Or with additional options: 37 | 38 | ```diff 39 | { 40 | "L3MON4D3/LuaSnip", 41 | version = "v2.*", 42 | dependencies = { 43 | + { 44 | + "mlaursen/vim-react-snippets", 45 | + opts = { 46 | + readonly_props = true, -- Set to `false` if all props should no longer be wrapped in `Readonly`. 47 | + test_framework = "@jest/globals", -- Set to "vitest" if you use vitest 48 | + test_renderer_path = "@testing-library/user-event", -- Set to a custom test renderer. For example "@/test-utils" 49 | + } 50 | + }, 51 | }, 52 | ``` 53 | 54 |
55 | 56 | With vim-plug and 57 | UltiSnips: 58 | 59 | 60 | ```vim 61 | call plug#begin('~/.vim/plugged') 62 | Plug 'SirVer/ultisnips' 63 | Plug 'mlaursen/vim-react-snippets' 64 | call plug#end() 65 | ``` 66 | 67 |
68 | 69 | > NOTE: I no longer use UltiSnips so the snippets might be different. Check out 70 | > the [UltiSnips folder] to see available snippets. 71 | 72 | ## Cheatsheet 73 | 74 | Most of the available snippets will be listed below to showcase the generated 75 | code. Tabstops will be indicated with `$TABSTOP` or `$NAME` where `$NAME` is 76 | replaceable. `$CFN` or `$CFN_` will indicate a snippet that uses the current 77 | file name to generate the code. 78 | 79 | Some snippets support an "inline" version as where the `const whatever =` will 80 | be omitted. These snippets will be marked with ✨. 81 | 82 | > Javascript snippets are not shown since I really only use Typescript now, but 83 | > they are generally the same without the type definitions included. 84 | 85 | ## Table of Contents 86 | 87 | 88 | 89 | - [Function Components](#function-components) 90 | - [Function Component Export](#function-component-export) 91 | - [Function Component Default Export](#function-component-default-export) 92 | - [Simple Function Component Export](#simple-function-component-export) 93 | - [Simple Function Component Default Export](#simple-function-component-default-export) 94 | - [Forwarded Function Component Export](#forwarded-function-component-export) 95 | - [Forwarded Function Component Default Export](#forwarded-function-component-default-export) 96 | - [Server Components](#server-components) 97 | - [Server Component Export](#server-component-export) 98 | - [Server Component Default Export](#server-component-default-export) 99 | - [Simple Server Component Export](#simple-server-component-export) 100 | - [Simple Server Component Default Export](#simple-server-component-default-export) 101 | - [Hooks and Effects](#hooks-and-effects) 102 | - [useState](#usestate) 103 | - [useReducer](#usereducer) 104 | - [useEffect](#useeffect) 105 | - [useContext ✨](#usecontext-) 106 | - [useCallback ✨](#usecallback-) 107 | - [useMemo ✨](#usememo-) 108 | - [useMemo return (manual return required) ✨](#usememo-return-manual-return-required-) 109 | - [useRef ✨](#useref-) 110 | - [Create Context Provider](#create-context-provider) 111 | - [Redux](#redux) 112 | - [useAppDispatch ✨](#useappdispatch-) 113 | - [useSelector ✨](#useselector-) 114 | - [useAppSelector ✨](#useappselector-) 115 | - [Common](#common) 116 | - [Destructured Const](#destructured-const) 117 | - [Export Destructured Const](#export-destructured-const) 118 | - [if](#if) 119 | - [else](#else) 120 | - [switch](#switch) 121 | - [for loop](#for-loop) 122 | - [reduce](#reduce) 123 | - [noop](#noop) 124 | - [interface](#interface) 125 | - [T generic](#t-generic) 126 | - [E extends HTMLElement](#e-extends-htmlelement) 127 | - [JSDoc/TSDoc](#jsdoctsdoc) 128 | - [Block Comment](#block-comment) 129 | - [@example](#example) 130 | - [@defaultValue](#defaultvalue) 131 | - [@since](#since) 132 | - [Logging](#logging) 133 | - [Importing](#importing) 134 | - [Exporting](#exporting) 135 | - [NODE_ENV](#node_env) 136 | - [Tests](#tests) 137 | - [Describe a test](#describe-a-test) 138 | - [it should](#it-should) 139 | - [it should (async)](#it-should-async) 140 | - [Test Expect](#test-expect) 141 | - [Test Queries ✨](#test-queries-) 142 | - [React Testing](#react-testing) 143 | - [React Test File](#react-test-file) 144 | - [React Test File (ESM)](#react-test-file-esm) 145 | - [Global Test File](#global-test-file) 146 | - [Global Test File (ESM)](#global-test-file-esm) 147 | - [User Event Test](#user-event-test) 148 | - [waitFor](#waitfor) 149 | - [SCSS Snippets](#scss-snippets) 150 | - [Contributing](#contributing) 151 | - [LuaSnip Template](#luasnip-template) 152 | 153 | 154 | ### Function Components 155 | 156 | #### Function Component Export 157 | 158 | `fce` -> 159 | 160 | ```tsx 161 | import { type ReactElement, type ReactNode } from "react" 162 | 163 | export interface $CFN_Props { 164 | $TABSTOP 165 | children: ReactNode 166 | } 167 | 168 | export function $CFN(props: Readonly<$CFN_Props>): ReactElement { 169 | return <> 170 | } 171 | ``` 172 | 173 | #### Function Component Default Export 174 | 175 | `fcde` -> 176 | 177 | ```tsx 178 | import { type ReactElement, type ReactNode } from "react" 179 | 180 | export interface $CFN_Props { 181 | $TABSTOP 182 | children: ReactNode 183 | } 184 | 185 | export default function $CFN(props: Readonly<$CFN_Props>): ReactElement { 186 | return <> 187 | } 188 | ``` 189 | 190 | #### Simple Function Component Export 191 | 192 | `sfce` -> 193 | 194 | ```tsx 195 | import { type ReactElement } from "react" 196 | 197 | export function $CFN(): ReactElement { 198 | return <> 199 | } 200 | ``` 201 | 202 | #### Simple Function Component Default Export 203 | 204 | `sfcde` -> 205 | 206 | ```tsx 207 | import { type ReactElement } from "react" 208 | 209 | export default function $CFN(): ReactElement { 210 | return <> 211 | } 212 | ``` 213 | 214 | #### Forwarded Function Component Export 215 | 216 | `ffce` -> 217 | 218 | ```tsx 219 | import { forwardRef, type ReactNode } from "react" 220 | 221 | export interface $CFNProps { 222 | $TABSTOP 223 | children: ReactNode 224 | } 225 | 226 | export const $CFN = forwardRef>( 227 | function $CFN(props, ref) { 228 | return
229 | } 230 | ) 231 | ``` 232 | 233 | #### Forwarded Function Component Default Export 234 | 235 | `ffcde` -> 236 | 237 | ```tsx 238 | import { forwardRef, type ReactNode } from "react" 239 | 240 | export interface $CFNProps { 241 | $TABSTOP 242 | children: ReactNode 243 | } 244 | 245 | export default forwardRef>( 246 | function $CFN(props, ref) { 247 | return
248 | } 249 | ) 250 | ``` 251 | 252 | ### Server Components 253 | 254 | #### Server Component Export 255 | 256 | `sce` -> 257 | 258 | ```tsx 259 | import { type ReactElement, type ReactNode } from "react" 260 | 261 | export interface $CFN_Props { 262 | $TABSTOP 263 | children: ReactNode 264 | } 265 | 266 | export function $CFN(props: Readonly<$CFN_Props>): Promise { 267 | return <> 268 | } 269 | ``` 270 | 271 | #### Server Component Default Export 272 | 273 | `scde` -> 274 | 275 | ```tsx 276 | import { type ReactElement, type ReactNode } from "react" 277 | 278 | export interface $CFN_Props { 279 | $TABSTOP 280 | children: ReactNode 281 | } 282 | 283 | export default function $CFN( 284 | props: Readonly<$CFN_Props> 285 | ): Promise { 286 | return <> 287 | } 288 | ``` 289 | 290 | #### Simple Server Component Export 291 | 292 | `sfce` -> 293 | 294 | ```tsx 295 | import { type ReactElement } from "react" 296 | 297 | export function $CFN(): Promise { 298 | return <> 299 | } 300 | ``` 301 | 302 | #### Simple Server Component Default Export 303 | 304 | `sscde` -> 305 | 306 | ```tsx 307 | import { type ReactElement } from "react" 308 | 309 | export default function $CFN(): Promise { 310 | return <> 311 | } 312 | ``` 313 | 314 | ### Hooks and Effects 315 | 316 | #### useState 317 | 318 | `useS` -> 319 | 320 | ```ts 321 | const [$STATE, set$STATE] = useState$TABSTOP($TABSTOP) 322 | ``` 323 | 324 | #### useReducer 325 | 326 | `useRed` -> 327 | 328 | ```tsx 329 | const [$STATE, $DISPATCH] = useReducer(function reducer(state: $STATE, action: $ACTION): $STATE { 330 | switch (action.type): 331 | default: 332 | return state 333 | }, $INITIAL_STATE) 334 | ``` 335 | 336 | #### useEffect 337 | 338 | `useE` -> 339 | 340 | ```ts 341 | useEffect(() => { 342 | $TABSTOP 343 | }, []) 344 | ``` 345 | 346 | #### useContext ✨ 347 | 348 | `useC` -> 349 | 350 | ```ts 351 | const context = useContext($TABSTOP) 352 | ``` 353 | 354 | #### useCallback ✨ 355 | 356 | `useCB` -> 357 | 358 | 359 | ```ts 360 | const $CALLBACK = useCallback(($TABSTOP) => { 361 | $TABSTOP 362 | }, []) 363 | ``` 364 | 365 | #### useMemo ✨ 366 | 367 | `useM` -> 368 | 369 | 370 | ```ts 371 | const $MEMOIZED = useMemo(() => ({ 372 | $TABSTOP 373 | }), []) 374 | ``` 375 | 376 | #### useMemo return (manual return required) ✨ 377 | 378 | `useMR` -> 379 | 380 | 381 | ```ts 382 | const $MEMOIZED = useMemo(() => { 383 | $TABSTOP 384 | }, []) 385 | ``` 386 | 387 | #### useRef ✨ 388 | 389 | `useR` -> 390 | 391 | ```ts 392 | const $REF = useRef$TABSTOP(TABSTOP) 393 | ``` 394 | 395 | #### Create Context Provider 396 | 397 | `ccp` -> 398 | 399 | ```tsx 400 | import { createContext, useContext } from "react" 401 | 402 | export interface $CFN_Context {} 403 | 404 | const context = createContext<$CFN_Context | null>(null) 405 | const { Provider } = context 406 | 407 | export function use$CFN_Context(): $CFN_Context { 408 | const value = useContext(context) 409 | if (!value) { 410 | throw new Error("$CFN_Context must be initialized.") 411 | } 412 | 413 | return value 414 | } 415 | ``` 416 | 417 | ### Redux 418 | 419 | #### useAppDispatch ✨ 420 | 421 | `useD` -> 422 | 423 | ```ts 424 | const dispatch = useAppDispatch() 425 | ``` 426 | 427 | #### useSelector ✨ 428 | 429 | `useSL` -> 430 | 431 | ```ts 432 | const $VALUE = useSelector(($STATE: AppState) => $SELECTOR) 433 | ``` 434 | 435 | #### useAppSelector ✨ 436 | 437 | `useAS` -> 438 | 439 | ```ts 440 | const $VALUE = useAppSelector($STATE) 441 | ``` 442 | 443 | ### Common 444 | 445 | #### Destructured Const 446 | 447 | `dc` -> 448 | 449 | ```ts 450 | const { $TABSTOP } = $PROPS 451 | ``` 452 | 453 | ```ts 454 | dcuseSomeHook() 455 | ^ trigger completion here 456 | 457 | const { $TABSTOP } = useSomeHook() 458 | ``` 459 | 460 | #### Export Destructured Const 461 | 462 | `edc` -> 463 | 464 | ```ts 465 | const { $TABSTOP } = $PROPS 466 | ``` 467 | 468 | #### if 469 | 470 | `if` -> 471 | 472 | ```ts 473 | if ($CONDITION) { 474 | $TABSTOP 475 | } 476 | ``` 477 | 478 | #### else 479 | 480 | `else` -> 481 | 482 | ```ts 483 | else $TABSTOP{ 484 | $TABSTOP 485 | } 486 | ``` 487 | 488 | The `if` snippet can be triggered from the first tabstop to generate: 489 | 490 | ```ts 491 | else if{ 492 | $TABSTOP 493 | } 494 | ``` 495 | 496 | into: 497 | 498 | ```ts 499 | else if ($CONDITION) { 500 | $TABSTOP 501 | } 502 | ``` 503 | 504 | #### switch 505 | 506 | `switch` -> 507 | 508 | ```ts 509 | switch ($KEY) { 510 | case $VALUE: 511 | $TABSTOP 512 | break 513 | default: 514 | $TABSTOP 515 | } 516 | ``` 517 | 518 | #### for loop 519 | 520 | `for` -> 521 | 522 | ```ts 523 | for (let $I = $0, $I < $LIST.length; $I++) { 524 | const $ITEM = $LIST[$I] 525 | $TABSTOP 526 | } 527 | ``` 528 | 529 | #### reduce 530 | 531 | `reduce` -> 532 | 533 | ```ts 534 | const $VALUE = $LIST.reduce<$TYPE_DEF>(($result, $item) => { 535 | $TABSTOP 536 | return $RESULT 537 | }, $INITIAL) 538 | ``` 539 | 540 | #### noop 541 | 542 | `noop` -> 543 | 544 | ```ts 545 | const noop = (): void => { 546 | // do nothing 547 | } 548 | ``` 549 | 550 | #### interface 551 | 552 | `intf` -> 553 | 554 | ```ts 555 | export interface $CFN_$TABSTOP { 556 | $TABSTOP 557 | } 558 | ``` 559 | 560 | #### T generic 561 | 562 | ` `` 563 | 564 | #### E extends HTMLElement 565 | 566 | ` `` 567 | 568 | ### JSDoc/TSDoc 569 | 570 | #### Block Comment 571 | 572 | `/**` -> 573 | 574 | ```ts 575 | /** 576 | * $TABSTOP 577 | */ 578 | ``` 579 | 580 | #### @example 581 | 582 | `@e` -> 583 | 584 | ```ts 585 | @example $EXAMPLE_NAME 586 | \`\`\`$TSX 587 | $TABSTOP 588 | \`\`\` 589 | ``` 590 | 591 | #### @defaultValue 592 | 593 | `@d` -> 594 | 595 | ```ts 596 | @defaultValue \`$TABSTOP\` 597 | ``` 598 | 599 | #### @since 600 | 601 | `@s` -> 602 | 603 | ```ts 604 | @since $MAJOR.$MINOR.$PATCH 605 | ``` 606 | 607 | ### Logging 608 | 609 | | Shortcut | Nane | Expands to | 610 | | -------- | ---------------------- | --------------------------------------- | 611 | | `cl` | Console Log | `console.log($TABSTOP)` | 612 | | `clv` | Console Log Variable | `console.log("$TABSTOP: ", $TABSTOP)` | 613 | | `ce` | Console Error | `console.error($TABSTOP)` | 614 | | `cev` | Console Error Variable | `console.error("$TABSTOP: ", $TABSTOP)` | 615 | | `cw` | Console Warn | `console.warn($TABSTOP)` | 616 | | `cwv` | Console Warn Variable | `console.warn("$TABSTOP: ", $TABSTOP)` | 617 | | `ct` | Console Table | `console.table($TABSTOP)` | 618 | | `cd` | Console Debug | `console.debug($TABSTOP)` | 619 | | `cdv` | Console Debug Variable | `console.debug("$TABSTOP: ", $TABSTOP)` | 620 | 621 | > Note: The logging commands that end in a `v` will have the cursor at the 622 | > second `$TABSTOP` instead of the first so that autocompletion will work. 623 | 624 | ### Importing 625 | 626 | | Shortcut | Name | Expands to | 627 | | -------- | -------------------- | ------------------------------------------------ | 628 | | `imp` | Import | `import packageName from "package-name"` | 629 | | `impf` | Import File | `import File from "./File"` | 630 | | `impd` | Import Destructured | `import { destructured } from "package-or/path"` | 631 | | `impp` | Import (Please?) | `import "./file"` | 632 | | `icn` | Import Classnames | `import cn from "classnames"` | 633 | | `icnb` | Import Cnbuilder | `import { cnb } from "cnbuilder"` | 634 | | `ism` | Import Styles Module | `import styles from "./$CFN.module.scss"` | 635 | 636 | ### Exporting 637 | 638 | | Shortcut | Name | Expands to | 639 | | -------- | ------------------- | --------------------------------------- | 640 | | `exp` | Export | `export { default } from "./$CFN"` | 641 | | `expf` | Export File | `export $TABSTOP from "./$TABSTOP"` | 642 | | `expd` | Export Destructured | `export { $TABSTOP } from "./$TABSTOP"` | 643 | | `expa` | Export All | `export * from "$TABSTOP"` | 644 | 645 | ### NODE_ENV 646 | 647 | | Shortcut | Expands to | 648 | | -------- | --------------------------------------- | 649 | | `dev` | `process.env.NODE_ENV !== "production"` | 650 | | `prod` | `process.env.NODE_ENV === "production"` | 651 | 652 | ### Tests 653 | 654 | #### Describe a test 655 | 656 | `desc` -> 657 | 658 | ```ts 659 | describe('$CFN', () => { 660 | it('should $TABSTOP', () => { 661 | $TABSTOP 662 | )} 663 | }) 664 | ``` 665 | 666 | #### it should 667 | 668 | `it` -> 669 | 670 | ```ts 671 | it("should $TABSTOP", () => { 672 | $TABSTOP 673 | }) 674 | ``` 675 | 676 | #### it should (async) 677 | 678 | `ita` -> 679 | 680 | ```ts 681 | it("should $TABSTOP", async () => { 682 | $TABSTOP 683 | }) 684 | ``` 685 | 686 | #### Test Expect 687 | 688 | | Shortcut | Name | Expands to | 689 | | -------- | ------------------- | -------------------------------------------------- | 690 | | `es` | Expect Snapshot | `expect(${container}).toMatchSnapshot()` | 691 | | `ett` | Expect To Throw | `expect(() => $TABSTOP).toThrow()` | 692 | | `entt` | Expect Not To Throw | `expect(() => $TABSTOP).not.toThrow()` | 693 | | `enc` | Expect Not Called | `expect($TABSTOP).not.toHaveBeenCalled()` | 694 | | `ecw` | Expect Called With | `expect($TABSTOP).toHaveBeenCalledWith($TABSTOP)` | 695 | | `ect` | Expect Called Times | `expect($TABSTOP).toHaveBeenCalledTimes($TABSTOP)` | 696 | 697 | #### Test Queries ✨ 698 | 699 | | Shortcut | Name | Expands to | 700 | | -------- | ----------------------------- | ----------------------------------------------------------------------------- | 701 | | `sgbr` | Screen Get By Role | `const $TABSTOP = screen.getByRole("${button}", { name: "$TABSTOP" })` | 702 | | `sgbru` | Screen Get By Role (Unnamed) | `const $TABSTOP = screen.getByRole("${progressbar}")` | 703 | | `sgbi` | Screen Get By testId | `const $TABSTOP = screen.getByTestId("$TABSTOP")` | 704 | | `sgbt` | Screen Get By Text | `const $TABSTOP = screen.getByText("$TABSTOP")` | 705 | | `sgbl` | Screen Get By Label Text | `const $TABSTOP = screen.getByLabelText("$TABSTOP")` | 706 | | `sfbr` | Screen Find By Role | `const $TABSTOP = await screen.findByRole("${button}", { name: "$TABSTOP" })` | 707 | | `sfbru` | Screen Find By Role (Unnamed) | `const $TABSTOP = await screen.findByRole("${progressbar}")` | 708 | | `fbi` | Screen Find By testId | `const $TABSTOP = await screen.findByTestId("$TABSTOP")` | 709 | | `fbt` | Screen Find By Text | `const $TABSTOP = await screen.findByText("$TABSTOP")` | 710 | | `fbl` | Screen Find By Label Text | `const $TABSTOP = await screen.findByLabelText("$TABSTOP")` | 711 | | `gbr` | Get By Role | `const $TABSTOP = getByRole("${button}", { name: "$TABSTOP" })` | 712 | | `gbru` | Get By Role (Unnamed) | `const $TABSTOP = getByRole("${progressbar}")` | 713 | | `gbi` | Get By testId | `const $TABSTOP = getByTestId("$TABSTOP")` | 714 | | `gbt` | Get By Text | `const $TABSTOP = getByText("$TABSTOP")` | 715 | | `fbr` | Find By Role | `const $TABSTOP = await findByRole("${button}", { name: "$TABSTOP" })` | 716 | | `fbru` | Find By Role (Unnamed) | `const $TABSTOP = await findByRole("${progressbar}")` | 717 | | `fbi` | Find By testId | `const $TABSTOP = await findByTestId("$TABSTOP")` | 718 | | `fbt` | Find By Text | `const $TABSTOP = await findByText("$TABSTOP")` | 719 | 720 | ### React Testing 721 | 722 | #### React Test File 723 | 724 | `rtf` -> 725 | 726 | ```tsx 727 | import { render, screen, userEvent } from "${@testing-library/react}" 728 | 729 | import { $CFN } from "../$CFN" 730 | 731 | describe("$CFN", () => { 732 | it("should $TABSTOP", () => { 733 | $TABSTOP 734 | }) 735 | }) 736 | ``` 737 | 738 | #### React Test File (ESM) 739 | 740 | `rtfe` -> 741 | 742 | ```tsx 743 | import { render, screen, userEvent } from "${@testing-library/react}" 744 | 745 | import { $CFN } from "../$CFN.js" 746 | 747 | describe("$CFN", () => { 748 | it("should $TABSTOP", () => { 749 | $TABSTOP 750 | }) 751 | }) 752 | ``` 753 | 754 | #### Global Test File 755 | 756 | `gtf` -> 757 | 758 | ```tsx 759 | import { describe, expect, it } from "${@jest/globals}" 760 | import { render, screen, userEvent } from "${@testing-library/react}" 761 | 762 | import { $CFN } from "../$CFN" 763 | 764 | describe("$CFN", () => { 765 | it("should $TABSTOP", () => { 766 | $TABSTOP 767 | }) 768 | }) 769 | ``` 770 | 771 | #### Global Test File (ESM) 772 | 773 | `gtfe` -> 774 | 775 | ```tsx 776 | import { describe, expect, it } from "${@jest/globals}" 777 | import { render, screen, userEvent } from "${@testing-library/react}" 778 | 779 | import { $CFN } from "../$CFN.js" 780 | 781 | describe("$CFN", () => { 782 | it("should $TABSTOP", () => { 783 | $TABSTOP 784 | }) 785 | }) 786 | ``` 787 | 788 | #### User Event Test 789 | 790 | `uet` -> 791 | 792 | ```tsx 793 | it("should $TABSTOP", async () => { 794 | const user = userEvent.setup() 795 | $TABSTOP 796 | 797 | expect(true).toBe(true) 798 | }) 799 | ``` 800 | 801 | #### waitFor 802 | 803 | `wf` -> 804 | 805 | ```tsx 806 | await waitFor(() => { 807 | $TABSTOP 808 | }) 809 | ``` 810 | 811 | ## SCSS Snippets 812 | 813 | | Shortcut | Name | Expands to | 814 | | -------- | -------------------- | --------------------------------------------------- | 815 | | `use` | Use | `@use "$TABSTOP";` | 816 | | `use*` | Use \* | `@use "$TABSTOP" as *;` | 817 | | `for` | Forward | `@forward "$TABSTOP" with ($TABSTOP);` | 818 | | `pcs` | Prefers Color Scheme | `@media (prefers-color-scheme: $DARK) { $TABSTOP }` | 819 | 820 | ## Contributing 821 | 822 | ### LuaSnip Template 823 | 824 | ```lua 825 | local ls = require("luasnip") 826 | local s = ls.snippet 827 | local sn = ls.snippet_node 828 | local isn = ls.indent_snippet_node 829 | local t = ls.text_node 830 | local i = ls.insert_node 831 | local f = ls.function_node 832 | local c = ls.choice_node 833 | local d = ls.dynamic_node 834 | local r = ls.restore_node 835 | local events = require("luasnip.util.events") 836 | local ai = require("luasnip.nodes.absolute_indexer") 837 | local extras = require("luasnip.extras") 838 | local l = extras.lambda 839 | local rep = extras.rep 840 | local p = extras.partial 841 | local m = extras.match 842 | local n = extras.nonempty 843 | local dl = extras.dynamic_lambda 844 | local fmt = require("luasnip.extras.fmt").fmt 845 | local fmta = require("luasnip.extras.fmt").fmta 846 | local conds = require("luasnip.extras.expand_conditions") 847 | local postfix = require("luasnip.extras.postfix").postfix 848 | local types = require("luasnip.util.types") 849 | local parse = require("luasnip.util.parser").parse_snippet 850 | local ms = ls.multi_snippet 851 | local k = require("luasnip.nodes.key_indexer").new_key 852 | ``` 853 | 854 | [react]: https://reactjs.org/ 855 | [ultisnips]: https://github.com/SirVer/ultisnips 856 | [ultisnips folder]: https://github.com/mlaursen/vim-react-snippets/tree/main/UltiSnips 857 | [luasnip]: https://github.com/L3MON4D3/LuaSnip 858 | [lazy.nvim]: https://github.com/folke/lazy.nvim 859 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | devDependencies: 11 | '@types/node': 12 | specifier: ^22.14.0 13 | version: 22.14.0 14 | husky: 15 | specifier: ^9.1.7 16 | version: 9.1.7 17 | lint-staged: 18 | specifier: ^15.5.0 19 | version: 15.5.0 20 | markdown-toc: 21 | specifier: ^1.2.0 22 | version: 1.2.0 23 | prettier: 24 | specifier: ^3.5.3 25 | version: 3.5.3 26 | tsx: 27 | specifier: ^4.19.3 28 | version: 4.19.3 29 | typescript: 30 | specifier: ^5.8.2 31 | version: 5.8.2 32 | 33 | packages: 34 | 35 | '@esbuild/aix-ppc64@0.25.1': 36 | resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==} 37 | engines: {node: '>=18'} 38 | cpu: [ppc64] 39 | os: [aix] 40 | 41 | '@esbuild/android-arm64@0.25.1': 42 | resolution: {integrity: sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==} 43 | engines: {node: '>=18'} 44 | cpu: [arm64] 45 | os: [android] 46 | 47 | '@esbuild/android-arm@0.25.1': 48 | resolution: {integrity: sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==} 49 | engines: {node: '>=18'} 50 | cpu: [arm] 51 | os: [android] 52 | 53 | '@esbuild/android-x64@0.25.1': 54 | resolution: {integrity: sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==} 55 | engines: {node: '>=18'} 56 | cpu: [x64] 57 | os: [android] 58 | 59 | '@esbuild/darwin-arm64@0.25.1': 60 | resolution: {integrity: sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==} 61 | engines: {node: '>=18'} 62 | cpu: [arm64] 63 | os: [darwin] 64 | 65 | '@esbuild/darwin-x64@0.25.1': 66 | resolution: {integrity: sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==} 67 | engines: {node: '>=18'} 68 | cpu: [x64] 69 | os: [darwin] 70 | 71 | '@esbuild/freebsd-arm64@0.25.1': 72 | resolution: {integrity: sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==} 73 | engines: {node: '>=18'} 74 | cpu: [arm64] 75 | os: [freebsd] 76 | 77 | '@esbuild/freebsd-x64@0.25.1': 78 | resolution: {integrity: sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==} 79 | engines: {node: '>=18'} 80 | cpu: [x64] 81 | os: [freebsd] 82 | 83 | '@esbuild/linux-arm64@0.25.1': 84 | resolution: {integrity: sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==} 85 | engines: {node: '>=18'} 86 | cpu: [arm64] 87 | os: [linux] 88 | 89 | '@esbuild/linux-arm@0.25.1': 90 | resolution: {integrity: sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==} 91 | engines: {node: '>=18'} 92 | cpu: [arm] 93 | os: [linux] 94 | 95 | '@esbuild/linux-ia32@0.25.1': 96 | resolution: {integrity: sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==} 97 | engines: {node: '>=18'} 98 | cpu: [ia32] 99 | os: [linux] 100 | 101 | '@esbuild/linux-loong64@0.25.1': 102 | resolution: {integrity: sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==} 103 | engines: {node: '>=18'} 104 | cpu: [loong64] 105 | os: [linux] 106 | 107 | '@esbuild/linux-mips64el@0.25.1': 108 | resolution: {integrity: sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==} 109 | engines: {node: '>=18'} 110 | cpu: [mips64el] 111 | os: [linux] 112 | 113 | '@esbuild/linux-ppc64@0.25.1': 114 | resolution: {integrity: sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==} 115 | engines: {node: '>=18'} 116 | cpu: [ppc64] 117 | os: [linux] 118 | 119 | '@esbuild/linux-riscv64@0.25.1': 120 | resolution: {integrity: sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==} 121 | engines: {node: '>=18'} 122 | cpu: [riscv64] 123 | os: [linux] 124 | 125 | '@esbuild/linux-s390x@0.25.1': 126 | resolution: {integrity: sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==} 127 | engines: {node: '>=18'} 128 | cpu: [s390x] 129 | os: [linux] 130 | 131 | '@esbuild/linux-x64@0.25.1': 132 | resolution: {integrity: sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==} 133 | engines: {node: '>=18'} 134 | cpu: [x64] 135 | os: [linux] 136 | 137 | '@esbuild/netbsd-arm64@0.25.1': 138 | resolution: {integrity: sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==} 139 | engines: {node: '>=18'} 140 | cpu: [arm64] 141 | os: [netbsd] 142 | 143 | '@esbuild/netbsd-x64@0.25.1': 144 | resolution: {integrity: sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==} 145 | engines: {node: '>=18'} 146 | cpu: [x64] 147 | os: [netbsd] 148 | 149 | '@esbuild/openbsd-arm64@0.25.1': 150 | resolution: {integrity: sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==} 151 | engines: {node: '>=18'} 152 | cpu: [arm64] 153 | os: [openbsd] 154 | 155 | '@esbuild/openbsd-x64@0.25.1': 156 | resolution: {integrity: sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==} 157 | engines: {node: '>=18'} 158 | cpu: [x64] 159 | os: [openbsd] 160 | 161 | '@esbuild/sunos-x64@0.25.1': 162 | resolution: {integrity: sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==} 163 | engines: {node: '>=18'} 164 | cpu: [x64] 165 | os: [sunos] 166 | 167 | '@esbuild/win32-arm64@0.25.1': 168 | resolution: {integrity: sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==} 169 | engines: {node: '>=18'} 170 | cpu: [arm64] 171 | os: [win32] 172 | 173 | '@esbuild/win32-ia32@0.25.1': 174 | resolution: {integrity: sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==} 175 | engines: {node: '>=18'} 176 | cpu: [ia32] 177 | os: [win32] 178 | 179 | '@esbuild/win32-x64@0.25.1': 180 | resolution: {integrity: sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==} 181 | engines: {node: '>=18'} 182 | cpu: [x64] 183 | os: [win32] 184 | 185 | '@types/node@22.14.0': 186 | resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==} 187 | 188 | ansi-escapes@7.0.0: 189 | resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} 190 | engines: {node: '>=18'} 191 | 192 | ansi-red@0.1.1: 193 | resolution: {integrity: sha512-ewaIr5y+9CUTGFwZfpECUbFlGcC0GCw1oqR9RI6h1gQCd9Aj2GxSckCnPsVJnmfMZbwFYE+leZGASgkWl06Jow==} 194 | engines: {node: '>=0.10.0'} 195 | 196 | ansi-regex@6.1.0: 197 | resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} 198 | engines: {node: '>=12'} 199 | 200 | ansi-styles@6.2.1: 201 | resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 202 | engines: {node: '>=12'} 203 | 204 | ansi-wrap@0.1.0: 205 | resolution: {integrity: sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==} 206 | engines: {node: '>=0.10.0'} 207 | 208 | argparse@1.0.10: 209 | resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} 210 | 211 | autolinker@0.28.1: 212 | resolution: {integrity: sha512-zQAFO1Dlsn69eXaO6+7YZc+v84aquQKbwpzCE3L0stj56ERn9hutFxPopViLjo9G+rWwjozRhgS5KJ25Xy19cQ==} 213 | 214 | braces@3.0.3: 215 | resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 216 | engines: {node: '>=8'} 217 | 218 | buffer-from@1.1.2: 219 | resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} 220 | 221 | chalk@5.4.1: 222 | resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} 223 | engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} 224 | 225 | cli-cursor@5.0.0: 226 | resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} 227 | engines: {node: '>=18'} 228 | 229 | cli-truncate@4.0.0: 230 | resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} 231 | engines: {node: '>=18'} 232 | 233 | coffee-script@1.12.7: 234 | resolution: {integrity: sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==} 235 | engines: {node: '>=0.8.0'} 236 | deprecated: CoffeeScript on NPM has moved to "coffeescript" (no hyphen) 237 | hasBin: true 238 | 239 | colorette@2.0.20: 240 | resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} 241 | 242 | commander@13.1.0: 243 | resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} 244 | engines: {node: '>=18'} 245 | 246 | concat-stream@1.6.2: 247 | resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} 248 | engines: {'0': node >= 0.8} 249 | 250 | concat-with-sourcemaps@1.1.0: 251 | resolution: {integrity: sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==} 252 | 253 | core-util-is@1.0.3: 254 | resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} 255 | 256 | cross-spawn@7.0.6: 257 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 258 | engines: {node: '>= 8'} 259 | 260 | debug@4.4.0: 261 | resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} 262 | engines: {node: '>=6.0'} 263 | peerDependencies: 264 | supports-color: '*' 265 | peerDependenciesMeta: 266 | supports-color: 267 | optional: true 268 | 269 | diacritics-map@0.1.0: 270 | resolution: {integrity: sha512-3omnDTYrGigU0i4cJjvaKwD52B8aoqyX/NEIkukFFkogBemsIbhSa1O414fpTp5nuszJG6lvQ5vBvDVNCbSsaQ==} 271 | engines: {node: '>=0.8.0'} 272 | 273 | emoji-regex@10.4.0: 274 | resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} 275 | 276 | environment@1.1.0: 277 | resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} 278 | engines: {node: '>=18'} 279 | 280 | esbuild@0.25.1: 281 | resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==} 282 | engines: {node: '>=18'} 283 | hasBin: true 284 | 285 | esprima@4.0.1: 286 | resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} 287 | engines: {node: '>=4'} 288 | hasBin: true 289 | 290 | eventemitter3@5.0.1: 291 | resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} 292 | 293 | execa@8.0.1: 294 | resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} 295 | engines: {node: '>=16.17'} 296 | 297 | expand-range@1.8.2: 298 | resolution: {integrity: sha512-AFASGfIlnIbkKPQwX1yHaDjFvh/1gyKJODme52V6IORh69uEYgZp0o9C+qsIGNVEiuuhQU0CSSl++Rlegg1qvA==} 299 | engines: {node: '>=0.10.0'} 300 | 301 | extend-shallow@2.0.1: 302 | resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} 303 | engines: {node: '>=0.10.0'} 304 | 305 | fill-range@2.2.4: 306 | resolution: {integrity: sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==} 307 | engines: {node: '>=0.10.0'} 308 | 309 | fill-range@7.1.1: 310 | resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 311 | engines: {node: '>=8'} 312 | 313 | for-in@1.0.2: 314 | resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} 315 | engines: {node: '>=0.10.0'} 316 | 317 | fsevents@2.3.3: 318 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 319 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 320 | os: [darwin] 321 | 322 | get-east-asian-width@1.3.0: 323 | resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} 324 | engines: {node: '>=18'} 325 | 326 | get-stream@8.0.1: 327 | resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} 328 | engines: {node: '>=16'} 329 | 330 | get-tsconfig@4.10.0: 331 | resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} 332 | 333 | gray-matter@2.1.1: 334 | resolution: {integrity: sha512-vbmvP1Fe/fxuT2QuLVcqb2BfK7upGhhbLIt9/owWEvPYrZZEkelLcq2HqzxosV+PQ67dUFLaAeNpH7C4hhICAA==} 335 | engines: {node: '>=0.10.0'} 336 | 337 | gulp-header@1.8.12: 338 | resolution: {integrity: sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==} 339 | deprecated: Removed event-stream from gulp-header 340 | 341 | human-signals@5.0.0: 342 | resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} 343 | engines: {node: '>=16.17.0'} 344 | 345 | husky@9.1.7: 346 | resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} 347 | engines: {node: '>=18'} 348 | hasBin: true 349 | 350 | inherits@2.0.4: 351 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 352 | 353 | is-buffer@1.1.6: 354 | resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} 355 | 356 | is-extendable@0.1.1: 357 | resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} 358 | engines: {node: '>=0.10.0'} 359 | 360 | is-extendable@1.0.1: 361 | resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} 362 | engines: {node: '>=0.10.0'} 363 | 364 | is-fullwidth-code-point@4.0.0: 365 | resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} 366 | engines: {node: '>=12'} 367 | 368 | is-fullwidth-code-point@5.0.0: 369 | resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} 370 | engines: {node: '>=18'} 371 | 372 | is-number@2.1.0: 373 | resolution: {integrity: sha512-QUzH43Gfb9+5yckcrSA0VBDwEtDUchrk4F6tfJZQuNzDJbEDB9cZNzSfXGQ1jqmdDY/kl41lUOWM9syA8z8jlg==} 374 | engines: {node: '>=0.10.0'} 375 | 376 | is-number@4.0.0: 377 | resolution: {integrity: sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==} 378 | engines: {node: '>=0.10.0'} 379 | 380 | is-number@7.0.0: 381 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 382 | engines: {node: '>=0.12.0'} 383 | 384 | is-plain-object@2.0.4: 385 | resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} 386 | engines: {node: '>=0.10.0'} 387 | 388 | is-stream@3.0.0: 389 | resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} 390 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 391 | 392 | isarray@1.0.0: 393 | resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} 394 | 395 | isexe@2.0.0: 396 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 397 | 398 | isobject@2.1.0: 399 | resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} 400 | engines: {node: '>=0.10.0'} 401 | 402 | isobject@3.0.1: 403 | resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} 404 | engines: {node: '>=0.10.0'} 405 | 406 | js-yaml@3.14.1: 407 | resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} 408 | hasBin: true 409 | 410 | kind-of@3.2.2: 411 | resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} 412 | engines: {node: '>=0.10.0'} 413 | 414 | kind-of@6.0.3: 415 | resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} 416 | engines: {node: '>=0.10.0'} 417 | 418 | lazy-cache@2.0.2: 419 | resolution: {integrity: sha512-7vp2Acd2+Kz4XkzxGxaB1FWOi8KjWIWsgdfD5MCb86DWvlLqhRPM+d6Pro3iNEL5VT9mstz5hKAlcd+QR6H3aA==} 420 | engines: {node: '>=0.10.0'} 421 | 422 | lilconfig@3.1.3: 423 | resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} 424 | engines: {node: '>=14'} 425 | 426 | lint-staged@15.5.0: 427 | resolution: {integrity: sha512-WyCzSbfYGhK7cU+UuDDkzUiytbfbi0ZdPy2orwtM75P3WTtQBzmG40cCxIa8Ii2+XjfxzLH6Be46tUfWS85Xfg==} 428 | engines: {node: '>=18.12.0'} 429 | hasBin: true 430 | 431 | list-item@1.1.1: 432 | resolution: {integrity: sha512-S3D0WZ4J6hyM8o5SNKWaMYB1ALSacPZ2nHGEuCjmHZ+dc03gFeNZoNDcqfcnO4vDhTZmNrqrpYZCdXsRh22bzw==} 433 | engines: {node: '>=0.10.0'} 434 | 435 | listr2@8.2.5: 436 | resolution: {integrity: sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==} 437 | engines: {node: '>=18.0.0'} 438 | 439 | lodash._reinterpolate@3.0.0: 440 | resolution: {integrity: sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==} 441 | 442 | lodash.template@4.5.0: 443 | resolution: {integrity: sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==} 444 | deprecated: This package is deprecated. Use https://socket.dev/npm/package/eta instead. 445 | 446 | lodash.templatesettings@4.2.0: 447 | resolution: {integrity: sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==} 448 | 449 | log-update@6.1.0: 450 | resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} 451 | engines: {node: '>=18'} 452 | 453 | markdown-link@0.1.1: 454 | resolution: {integrity: sha512-TurLymbyLyo+kAUUAV9ggR9EPcDjP/ctlv9QAFiqUH7c+t6FlsbivPo9OKTU8xdOx9oNd2drW/Fi5RRElQbUqA==} 455 | engines: {node: '>=0.10.0'} 456 | 457 | markdown-toc@1.2.0: 458 | resolution: {integrity: sha512-eOsq7EGd3asV0oBfmyqngeEIhrbkc7XVP63OwcJBIhH2EpG2PzFcbZdhy1jutXSlRBBVMNXHvMtSr5LAxSUvUg==} 459 | engines: {node: '>=0.10.0'} 460 | hasBin: true 461 | 462 | math-random@1.0.4: 463 | resolution: {integrity: sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==} 464 | 465 | merge-stream@2.0.0: 466 | resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} 467 | 468 | micromatch@4.0.8: 469 | resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} 470 | engines: {node: '>=8.6'} 471 | 472 | mimic-fn@4.0.0: 473 | resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} 474 | engines: {node: '>=12'} 475 | 476 | mimic-function@5.0.1: 477 | resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} 478 | engines: {node: '>=18'} 479 | 480 | minimist@1.2.8: 481 | resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} 482 | 483 | mixin-deep@1.3.2: 484 | resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} 485 | engines: {node: '>=0.10.0'} 486 | 487 | ms@2.1.3: 488 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 489 | 490 | npm-run-path@5.3.0: 491 | resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} 492 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 493 | 494 | object.pick@1.3.0: 495 | resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==} 496 | engines: {node: '>=0.10.0'} 497 | 498 | onetime@6.0.0: 499 | resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} 500 | engines: {node: '>=12'} 501 | 502 | onetime@7.0.0: 503 | resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} 504 | engines: {node: '>=18'} 505 | 506 | path-key@3.1.1: 507 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 508 | engines: {node: '>=8'} 509 | 510 | path-key@4.0.0: 511 | resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} 512 | engines: {node: '>=12'} 513 | 514 | picomatch@2.3.1: 515 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 516 | engines: {node: '>=8.6'} 517 | 518 | pidtree@0.6.0: 519 | resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} 520 | engines: {node: '>=0.10'} 521 | hasBin: true 522 | 523 | prettier@3.5.3: 524 | resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} 525 | engines: {node: '>=14'} 526 | hasBin: true 527 | 528 | process-nextick-args@2.0.1: 529 | resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} 530 | 531 | randomatic@3.1.1: 532 | resolution: {integrity: sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==} 533 | engines: {node: '>= 0.10.0'} 534 | 535 | readable-stream@2.3.8: 536 | resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} 537 | 538 | remarkable@1.7.4: 539 | resolution: {integrity: sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg==} 540 | engines: {node: '>= 0.10.0'} 541 | hasBin: true 542 | 543 | repeat-element@1.1.4: 544 | resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==} 545 | engines: {node: '>=0.10.0'} 546 | 547 | repeat-string@1.6.1: 548 | resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} 549 | engines: {node: '>=0.10'} 550 | 551 | resolve-pkg-maps@1.0.0: 552 | resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} 553 | 554 | restore-cursor@5.1.0: 555 | resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} 556 | engines: {node: '>=18'} 557 | 558 | rfdc@1.4.1: 559 | resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} 560 | 561 | safe-buffer@5.1.2: 562 | resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} 563 | 564 | set-getter@0.1.1: 565 | resolution: {integrity: sha512-9sVWOy+gthr+0G9DzqqLaYNA7+5OKkSmcqjL9cBpDEaZrr3ShQlyX2cZ/O/ozE41oxn/Tt0LGEM/w4Rub3A3gw==} 566 | engines: {node: '>=0.10.0'} 567 | 568 | shebang-command@2.0.0: 569 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 570 | engines: {node: '>=8'} 571 | 572 | shebang-regex@3.0.0: 573 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 574 | engines: {node: '>=8'} 575 | 576 | signal-exit@4.1.0: 577 | resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 578 | engines: {node: '>=14'} 579 | 580 | slice-ansi@5.0.0: 581 | resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} 582 | engines: {node: '>=12'} 583 | 584 | slice-ansi@7.1.0: 585 | resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} 586 | engines: {node: '>=18'} 587 | 588 | source-map@0.6.1: 589 | resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 590 | engines: {node: '>=0.10.0'} 591 | 592 | sprintf-js@1.0.3: 593 | resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} 594 | 595 | string-argv@0.3.2: 596 | resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} 597 | engines: {node: '>=0.6.19'} 598 | 599 | string-width@7.2.0: 600 | resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} 601 | engines: {node: '>=18'} 602 | 603 | string_decoder@1.1.1: 604 | resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} 605 | 606 | strip-ansi@7.1.0: 607 | resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 608 | engines: {node: '>=12'} 609 | 610 | strip-color@0.1.0: 611 | resolution: {integrity: sha512-p9LsUieSjWNNAxVCXLeilaDlmuUOrDS5/dF9znM1nZc7EGX5+zEFC0bEevsNIaldjlks+2jns5Siz6F9iK6jwA==} 612 | engines: {node: '>=0.10.0'} 613 | 614 | strip-final-newline@3.0.0: 615 | resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} 616 | engines: {node: '>=12'} 617 | 618 | through2@2.0.5: 619 | resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} 620 | 621 | to-object-path@0.3.0: 622 | resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} 623 | engines: {node: '>=0.10.0'} 624 | 625 | to-regex-range@5.0.1: 626 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 627 | engines: {node: '>=8.0'} 628 | 629 | toml@2.3.6: 630 | resolution: {integrity: sha512-gVweAectJU3ebq//Ferr2JUY4WKSDe5N+z0FvjDncLGyHmIDoxgY/2Ie4qfEIDm4IS7OA6Rmdm7pdEEdMcV/xQ==} 631 | 632 | tsx@4.19.3: 633 | resolution: {integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==} 634 | engines: {node: '>=18.0.0'} 635 | hasBin: true 636 | 637 | typedarray@0.0.6: 638 | resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} 639 | 640 | typescript@5.8.2: 641 | resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} 642 | engines: {node: '>=14.17'} 643 | hasBin: true 644 | 645 | undici-types@6.21.0: 646 | resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} 647 | 648 | util-deprecate@1.0.2: 649 | resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 650 | 651 | which@2.0.2: 652 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 653 | engines: {node: '>= 8'} 654 | hasBin: true 655 | 656 | wrap-ansi@9.0.0: 657 | resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} 658 | engines: {node: '>=18'} 659 | 660 | xtend@4.0.2: 661 | resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} 662 | engines: {node: '>=0.4'} 663 | 664 | yaml@2.7.0: 665 | resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} 666 | engines: {node: '>= 14'} 667 | hasBin: true 668 | 669 | snapshots: 670 | 671 | '@esbuild/aix-ppc64@0.25.1': 672 | optional: true 673 | 674 | '@esbuild/android-arm64@0.25.1': 675 | optional: true 676 | 677 | '@esbuild/android-arm@0.25.1': 678 | optional: true 679 | 680 | '@esbuild/android-x64@0.25.1': 681 | optional: true 682 | 683 | '@esbuild/darwin-arm64@0.25.1': 684 | optional: true 685 | 686 | '@esbuild/darwin-x64@0.25.1': 687 | optional: true 688 | 689 | '@esbuild/freebsd-arm64@0.25.1': 690 | optional: true 691 | 692 | '@esbuild/freebsd-x64@0.25.1': 693 | optional: true 694 | 695 | '@esbuild/linux-arm64@0.25.1': 696 | optional: true 697 | 698 | '@esbuild/linux-arm@0.25.1': 699 | optional: true 700 | 701 | '@esbuild/linux-ia32@0.25.1': 702 | optional: true 703 | 704 | '@esbuild/linux-loong64@0.25.1': 705 | optional: true 706 | 707 | '@esbuild/linux-mips64el@0.25.1': 708 | optional: true 709 | 710 | '@esbuild/linux-ppc64@0.25.1': 711 | optional: true 712 | 713 | '@esbuild/linux-riscv64@0.25.1': 714 | optional: true 715 | 716 | '@esbuild/linux-s390x@0.25.1': 717 | optional: true 718 | 719 | '@esbuild/linux-x64@0.25.1': 720 | optional: true 721 | 722 | '@esbuild/netbsd-arm64@0.25.1': 723 | optional: true 724 | 725 | '@esbuild/netbsd-x64@0.25.1': 726 | optional: true 727 | 728 | '@esbuild/openbsd-arm64@0.25.1': 729 | optional: true 730 | 731 | '@esbuild/openbsd-x64@0.25.1': 732 | optional: true 733 | 734 | '@esbuild/sunos-x64@0.25.1': 735 | optional: true 736 | 737 | '@esbuild/win32-arm64@0.25.1': 738 | optional: true 739 | 740 | '@esbuild/win32-ia32@0.25.1': 741 | optional: true 742 | 743 | '@esbuild/win32-x64@0.25.1': 744 | optional: true 745 | 746 | '@types/node@22.14.0': 747 | dependencies: 748 | undici-types: 6.21.0 749 | 750 | ansi-escapes@7.0.0: 751 | dependencies: 752 | environment: 1.1.0 753 | 754 | ansi-red@0.1.1: 755 | dependencies: 756 | ansi-wrap: 0.1.0 757 | 758 | ansi-regex@6.1.0: {} 759 | 760 | ansi-styles@6.2.1: {} 761 | 762 | ansi-wrap@0.1.0: {} 763 | 764 | argparse@1.0.10: 765 | dependencies: 766 | sprintf-js: 1.0.3 767 | 768 | autolinker@0.28.1: 769 | dependencies: 770 | gulp-header: 1.8.12 771 | 772 | braces@3.0.3: 773 | dependencies: 774 | fill-range: 7.1.1 775 | 776 | buffer-from@1.1.2: {} 777 | 778 | chalk@5.4.1: {} 779 | 780 | cli-cursor@5.0.0: 781 | dependencies: 782 | restore-cursor: 5.1.0 783 | 784 | cli-truncate@4.0.0: 785 | dependencies: 786 | slice-ansi: 5.0.0 787 | string-width: 7.2.0 788 | 789 | coffee-script@1.12.7: {} 790 | 791 | colorette@2.0.20: {} 792 | 793 | commander@13.1.0: {} 794 | 795 | concat-stream@1.6.2: 796 | dependencies: 797 | buffer-from: 1.1.2 798 | inherits: 2.0.4 799 | readable-stream: 2.3.8 800 | typedarray: 0.0.6 801 | 802 | concat-with-sourcemaps@1.1.0: 803 | dependencies: 804 | source-map: 0.6.1 805 | 806 | core-util-is@1.0.3: {} 807 | 808 | cross-spawn@7.0.6: 809 | dependencies: 810 | path-key: 3.1.1 811 | shebang-command: 2.0.0 812 | which: 2.0.2 813 | 814 | debug@4.4.0: 815 | dependencies: 816 | ms: 2.1.3 817 | 818 | diacritics-map@0.1.0: {} 819 | 820 | emoji-regex@10.4.0: {} 821 | 822 | environment@1.1.0: {} 823 | 824 | esbuild@0.25.1: 825 | optionalDependencies: 826 | '@esbuild/aix-ppc64': 0.25.1 827 | '@esbuild/android-arm': 0.25.1 828 | '@esbuild/android-arm64': 0.25.1 829 | '@esbuild/android-x64': 0.25.1 830 | '@esbuild/darwin-arm64': 0.25.1 831 | '@esbuild/darwin-x64': 0.25.1 832 | '@esbuild/freebsd-arm64': 0.25.1 833 | '@esbuild/freebsd-x64': 0.25.1 834 | '@esbuild/linux-arm': 0.25.1 835 | '@esbuild/linux-arm64': 0.25.1 836 | '@esbuild/linux-ia32': 0.25.1 837 | '@esbuild/linux-loong64': 0.25.1 838 | '@esbuild/linux-mips64el': 0.25.1 839 | '@esbuild/linux-ppc64': 0.25.1 840 | '@esbuild/linux-riscv64': 0.25.1 841 | '@esbuild/linux-s390x': 0.25.1 842 | '@esbuild/linux-x64': 0.25.1 843 | '@esbuild/netbsd-arm64': 0.25.1 844 | '@esbuild/netbsd-x64': 0.25.1 845 | '@esbuild/openbsd-arm64': 0.25.1 846 | '@esbuild/openbsd-x64': 0.25.1 847 | '@esbuild/sunos-x64': 0.25.1 848 | '@esbuild/win32-arm64': 0.25.1 849 | '@esbuild/win32-ia32': 0.25.1 850 | '@esbuild/win32-x64': 0.25.1 851 | 852 | esprima@4.0.1: {} 853 | 854 | eventemitter3@5.0.1: {} 855 | 856 | execa@8.0.1: 857 | dependencies: 858 | cross-spawn: 7.0.6 859 | get-stream: 8.0.1 860 | human-signals: 5.0.0 861 | is-stream: 3.0.0 862 | merge-stream: 2.0.0 863 | npm-run-path: 5.3.0 864 | onetime: 6.0.0 865 | signal-exit: 4.1.0 866 | strip-final-newline: 3.0.0 867 | 868 | expand-range@1.8.2: 869 | dependencies: 870 | fill-range: 2.2.4 871 | 872 | extend-shallow@2.0.1: 873 | dependencies: 874 | is-extendable: 0.1.1 875 | 876 | fill-range@2.2.4: 877 | dependencies: 878 | is-number: 2.1.0 879 | isobject: 2.1.0 880 | randomatic: 3.1.1 881 | repeat-element: 1.1.4 882 | repeat-string: 1.6.1 883 | 884 | fill-range@7.1.1: 885 | dependencies: 886 | to-regex-range: 5.0.1 887 | 888 | for-in@1.0.2: {} 889 | 890 | fsevents@2.3.3: 891 | optional: true 892 | 893 | get-east-asian-width@1.3.0: {} 894 | 895 | get-stream@8.0.1: {} 896 | 897 | get-tsconfig@4.10.0: 898 | dependencies: 899 | resolve-pkg-maps: 1.0.0 900 | 901 | gray-matter@2.1.1: 902 | dependencies: 903 | ansi-red: 0.1.1 904 | coffee-script: 1.12.7 905 | extend-shallow: 2.0.1 906 | js-yaml: 3.14.1 907 | toml: 2.3.6 908 | 909 | gulp-header@1.8.12: 910 | dependencies: 911 | concat-with-sourcemaps: 1.1.0 912 | lodash.template: 4.5.0 913 | through2: 2.0.5 914 | 915 | human-signals@5.0.0: {} 916 | 917 | husky@9.1.7: {} 918 | 919 | inherits@2.0.4: {} 920 | 921 | is-buffer@1.1.6: {} 922 | 923 | is-extendable@0.1.1: {} 924 | 925 | is-extendable@1.0.1: 926 | dependencies: 927 | is-plain-object: 2.0.4 928 | 929 | is-fullwidth-code-point@4.0.0: {} 930 | 931 | is-fullwidth-code-point@5.0.0: 932 | dependencies: 933 | get-east-asian-width: 1.3.0 934 | 935 | is-number@2.1.0: 936 | dependencies: 937 | kind-of: 3.2.2 938 | 939 | is-number@4.0.0: {} 940 | 941 | is-number@7.0.0: {} 942 | 943 | is-plain-object@2.0.4: 944 | dependencies: 945 | isobject: 3.0.1 946 | 947 | is-stream@3.0.0: {} 948 | 949 | isarray@1.0.0: {} 950 | 951 | isexe@2.0.0: {} 952 | 953 | isobject@2.1.0: 954 | dependencies: 955 | isarray: 1.0.0 956 | 957 | isobject@3.0.1: {} 958 | 959 | js-yaml@3.14.1: 960 | dependencies: 961 | argparse: 1.0.10 962 | esprima: 4.0.1 963 | 964 | kind-of@3.2.2: 965 | dependencies: 966 | is-buffer: 1.1.6 967 | 968 | kind-of@6.0.3: {} 969 | 970 | lazy-cache@2.0.2: 971 | dependencies: 972 | set-getter: 0.1.1 973 | 974 | lilconfig@3.1.3: {} 975 | 976 | lint-staged@15.5.0: 977 | dependencies: 978 | chalk: 5.4.1 979 | commander: 13.1.0 980 | debug: 4.4.0 981 | execa: 8.0.1 982 | lilconfig: 3.1.3 983 | listr2: 8.2.5 984 | micromatch: 4.0.8 985 | pidtree: 0.6.0 986 | string-argv: 0.3.2 987 | yaml: 2.7.0 988 | transitivePeerDependencies: 989 | - supports-color 990 | 991 | list-item@1.1.1: 992 | dependencies: 993 | expand-range: 1.8.2 994 | extend-shallow: 2.0.1 995 | is-number: 2.1.0 996 | repeat-string: 1.6.1 997 | 998 | listr2@8.2.5: 999 | dependencies: 1000 | cli-truncate: 4.0.0 1001 | colorette: 2.0.20 1002 | eventemitter3: 5.0.1 1003 | log-update: 6.1.0 1004 | rfdc: 1.4.1 1005 | wrap-ansi: 9.0.0 1006 | 1007 | lodash._reinterpolate@3.0.0: {} 1008 | 1009 | lodash.template@4.5.0: 1010 | dependencies: 1011 | lodash._reinterpolate: 3.0.0 1012 | lodash.templatesettings: 4.2.0 1013 | 1014 | lodash.templatesettings@4.2.0: 1015 | dependencies: 1016 | lodash._reinterpolate: 3.0.0 1017 | 1018 | log-update@6.1.0: 1019 | dependencies: 1020 | ansi-escapes: 7.0.0 1021 | cli-cursor: 5.0.0 1022 | slice-ansi: 7.1.0 1023 | strip-ansi: 7.1.0 1024 | wrap-ansi: 9.0.0 1025 | 1026 | markdown-link@0.1.1: {} 1027 | 1028 | markdown-toc@1.2.0: 1029 | dependencies: 1030 | concat-stream: 1.6.2 1031 | diacritics-map: 0.1.0 1032 | gray-matter: 2.1.1 1033 | lazy-cache: 2.0.2 1034 | list-item: 1.1.1 1035 | markdown-link: 0.1.1 1036 | minimist: 1.2.8 1037 | mixin-deep: 1.3.2 1038 | object.pick: 1.3.0 1039 | remarkable: 1.7.4 1040 | repeat-string: 1.6.1 1041 | strip-color: 0.1.0 1042 | 1043 | math-random@1.0.4: {} 1044 | 1045 | merge-stream@2.0.0: {} 1046 | 1047 | micromatch@4.0.8: 1048 | dependencies: 1049 | braces: 3.0.3 1050 | picomatch: 2.3.1 1051 | 1052 | mimic-fn@4.0.0: {} 1053 | 1054 | mimic-function@5.0.1: {} 1055 | 1056 | minimist@1.2.8: {} 1057 | 1058 | mixin-deep@1.3.2: 1059 | dependencies: 1060 | for-in: 1.0.2 1061 | is-extendable: 1.0.1 1062 | 1063 | ms@2.1.3: {} 1064 | 1065 | npm-run-path@5.3.0: 1066 | dependencies: 1067 | path-key: 4.0.0 1068 | 1069 | object.pick@1.3.0: 1070 | dependencies: 1071 | isobject: 3.0.1 1072 | 1073 | onetime@6.0.0: 1074 | dependencies: 1075 | mimic-fn: 4.0.0 1076 | 1077 | onetime@7.0.0: 1078 | dependencies: 1079 | mimic-function: 5.0.1 1080 | 1081 | path-key@3.1.1: {} 1082 | 1083 | path-key@4.0.0: {} 1084 | 1085 | picomatch@2.3.1: {} 1086 | 1087 | pidtree@0.6.0: {} 1088 | 1089 | prettier@3.5.3: {} 1090 | 1091 | process-nextick-args@2.0.1: {} 1092 | 1093 | randomatic@3.1.1: 1094 | dependencies: 1095 | is-number: 4.0.0 1096 | kind-of: 6.0.3 1097 | math-random: 1.0.4 1098 | 1099 | readable-stream@2.3.8: 1100 | dependencies: 1101 | core-util-is: 1.0.3 1102 | inherits: 2.0.4 1103 | isarray: 1.0.0 1104 | process-nextick-args: 2.0.1 1105 | safe-buffer: 5.1.2 1106 | string_decoder: 1.1.1 1107 | util-deprecate: 1.0.2 1108 | 1109 | remarkable@1.7.4: 1110 | dependencies: 1111 | argparse: 1.0.10 1112 | autolinker: 0.28.1 1113 | 1114 | repeat-element@1.1.4: {} 1115 | 1116 | repeat-string@1.6.1: {} 1117 | 1118 | resolve-pkg-maps@1.0.0: {} 1119 | 1120 | restore-cursor@5.1.0: 1121 | dependencies: 1122 | onetime: 7.0.0 1123 | signal-exit: 4.1.0 1124 | 1125 | rfdc@1.4.1: {} 1126 | 1127 | safe-buffer@5.1.2: {} 1128 | 1129 | set-getter@0.1.1: 1130 | dependencies: 1131 | to-object-path: 0.3.0 1132 | 1133 | shebang-command@2.0.0: 1134 | dependencies: 1135 | shebang-regex: 3.0.0 1136 | 1137 | shebang-regex@3.0.0: {} 1138 | 1139 | signal-exit@4.1.0: {} 1140 | 1141 | slice-ansi@5.0.0: 1142 | dependencies: 1143 | ansi-styles: 6.2.1 1144 | is-fullwidth-code-point: 4.0.0 1145 | 1146 | slice-ansi@7.1.0: 1147 | dependencies: 1148 | ansi-styles: 6.2.1 1149 | is-fullwidth-code-point: 5.0.0 1150 | 1151 | source-map@0.6.1: {} 1152 | 1153 | sprintf-js@1.0.3: {} 1154 | 1155 | string-argv@0.3.2: {} 1156 | 1157 | string-width@7.2.0: 1158 | dependencies: 1159 | emoji-regex: 10.4.0 1160 | get-east-asian-width: 1.3.0 1161 | strip-ansi: 7.1.0 1162 | 1163 | string_decoder@1.1.1: 1164 | dependencies: 1165 | safe-buffer: 5.1.2 1166 | 1167 | strip-ansi@7.1.0: 1168 | dependencies: 1169 | ansi-regex: 6.1.0 1170 | 1171 | strip-color@0.1.0: {} 1172 | 1173 | strip-final-newline@3.0.0: {} 1174 | 1175 | through2@2.0.5: 1176 | dependencies: 1177 | readable-stream: 2.3.8 1178 | xtend: 4.0.2 1179 | 1180 | to-object-path@0.3.0: 1181 | dependencies: 1182 | kind-of: 3.2.2 1183 | 1184 | to-regex-range@5.0.1: 1185 | dependencies: 1186 | is-number: 7.0.0 1187 | 1188 | toml@2.3.6: {} 1189 | 1190 | tsx@4.19.3: 1191 | dependencies: 1192 | esbuild: 0.25.1 1193 | get-tsconfig: 4.10.0 1194 | optionalDependencies: 1195 | fsevents: 2.3.3 1196 | 1197 | typedarray@0.0.6: {} 1198 | 1199 | typescript@5.8.2: {} 1200 | 1201 | undici-types@6.21.0: {} 1202 | 1203 | util-deprecate@1.0.2: {} 1204 | 1205 | which@2.0.2: 1206 | dependencies: 1207 | isexe: 2.0.0 1208 | 1209 | wrap-ansi@9.0.0: 1210 | dependencies: 1211 | ansi-styles: 6.2.1 1212 | string-width: 7.2.0 1213 | strip-ansi: 7.1.0 1214 | 1215 | xtend@4.0.2: {} 1216 | 1217 | yaml@2.7.0: {} 1218 | --------------------------------------------------------------------------------