src/snippets/vscode-snippet.ts (184 lines of code) (raw):
/*
Copyright (c) Uber Technologies, Inc.
This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
import { clone } from "../utils";
import {
TProp,
TPropValue,
TImportsConfig,
PropTypes,
formatCode as rvFormatCode,
} from "../";
import { addToImportList } from "../code-generator";
const formatCode = (code: TPropValue) => {
if (code && typeof code === "string") {
const isJsx = code.startsWith("<");
try {
if (isJsx) {
code = rvFormatCode(`<>${code}</>`);
} else {
code = rvFormatCode(`<>{${code}}</>`);
}
const addSpaces = !code.startsWith("<>\n");
if (isJsx) {
code = code.replace(/^<>\s*/, "").replace(/\s*<\/>$/, "");
} else {
code = code.replace(/^<>\s*\{/, "").replace(/\}\s*<\/>$/, "");
}
if (addSpaces) {
code = code.replace(/\n/g, "\n ");
}
} catch (e) {}
code = code.replace(/\}/g, "\\}").replace(/\$/g, "\\$");
}
return code;
};
const joinNamed = (items: string[] | undefined, ctr: number) => {
if (!items) return "";
let output = `\${${ctr++}:{`;
for (let i = 0; i < items.length; i++) {
if (i !== items.length - 1) {
output += `\${${ctr++}:${items[i]}, }`;
} else {
output += `\${${ctr++}:${items[i]}}`;
}
}
return `${output}\\}}`;
};
type TVscodeSnippetOutput = {
[key: string]: {
body: string[];
description: string;
prefix: string[];
scope: string;
};
};
type TVscodeSnippet = (props: {
componentName: string;
prefix?: string;
imports?: TImportsConfig;
props?: { [key: string]: TProp<any> };
description?: string;
}) => TVscodeSnippetOutput;
const getImportBody = (
imports?: TImportsConfig,
props?: { [key: string]: TProp<any> },
) => {
const importList: TImportsConfig = imports ? clone(imports) : {};
// prop level imports (typically enums related) that are displayed
// only when the prop is being used
props &&
Object.values(props).forEach((prop) => {
if (prop.imports) {
addToImportList(importList, prop.imports);
}
});
const importBody: string[] = [];
let ctr = 1;
for (const from in importList) {
const def = importList[from].default;
const named =
Array.isArray(importList[from].named) &&
(importList[from].named as string[]).length > 0
? importList[from].named
: undefined;
const defaultImport = def ? `\${${ctr++}:${def}${named ? ", }" : "}"}` : "";
importBody.push(
`import ${defaultImport}${joinNamed(named, ctr)} from '${from}';`,
);
if (named) {
ctr += named.length + 1;
}
}
return importBody;
};
const getComponentBody = (
componentName: string,
props?: { [key: string]: TProp<any> },
) => {
let ctr = 1;
const componentBody = [`<${componentName}`];
if (props) {
for (const propName in props) {
if (props[propName].hidden) continue;
if (propName === "children") continue;
if (props[propName].type === PropTypes.Boolean) {
const row = ` \${${ctr++}:${propName}}`;
componentBody.push(row);
} else if (props[propName].type === PropTypes.Enum) {
const enumName = props[propName].imports
? props[propName].enumName || propName.toUpperCase()
: null;
const opts = Object.values(props[propName].options)
.map((opt: any) =>
enumName
? opt.includes("-")
? `${enumName}['${opt}']`
: `${enumName}.${opt}`
: opt,
)
.filter((opt) => opt !== props[propName].defaultValue);
if (props[propName].defaultValue) {
opts.unshift(props[propName].defaultValue as string);
}
if (!props[propName].imports) {
const row = ` \${${ctr++}:${propName}="\${${ctr++}|${opts.join(
",",
)}|}\"}`;
componentBody.push(row);
} else {
const row = ` \${${ctr++}:${propName}={\${${ctr++}|${opts.join(
",",
)}|}\\}}`;
componentBody.push(row);
}
} else if (
props[propName].type === PropTypes.String &&
typeof props[propName].value === PropTypes.String
) {
const row = ` \${${ctr++}:${propName}="\${${ctr++}:${formatCode(
props[propName].defaultValue || props[propName].value,
)}}\"}`;
componentBody.push(row);
} else {
const row = ` \${${ctr++}:${propName}={\${${ctr++}:${formatCode(
props[propName].defaultValue || props[propName].value,
)}}\\}}`;
componentBody.push(row);
}
}
if (props["children"] && !props["children"].hidden) {
componentBody.push(">");
componentBody.push(
` \${${ctr++}:${formatCode(props["children"].value)}}`,
);
componentBody.push(`</${componentName}>`);
} else {
componentBody.push(`/>`);
}
} else {
componentBody.push(`/>`);
}
return componentBody;
};
const vscodeSnippet: TVscodeSnippet = ({
componentName,
prefix,
imports,
props,
description,
}) => {
const output: TVscodeSnippetOutput = {};
const importBody = getImportBody(imports, props);
if (importBody.length > 0) {
output[`${componentName} import`] = {
scope: "javascript,javascriptreact,typescript,typescriptreact",
prefix: [`${prefix || componentName} import`],
description: description || `Base ${componentName} import.`,
body: importBody,
};
}
output[`${componentName}`] = {
scope: "javascript,javascriptreact,typescript,typescriptreact",
prefix: [`${prefix || componentName} component`],
description: description || `Base ${componentName} component.`,
body: getComponentBody(componentName, props),
};
return output;
};
export default vscodeSnippet;