UNPKG

9.98 kBJavaScriptView Raw
1"use strict";
2var __rest = (this && this.__rest) || function (s, e) {
3 var t = {};
4 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5 t[p] = s[p];
6 if (s != null && typeof Object.getOwnPropertySymbols === "function")
7 for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8 if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9 t[p[i]] = s[p[i]];
10 }
11 return t;
12};
13var __importDefault = (this && this.__importDefault) || function (mod) {
14 return (mod && mod.__esModule) ? mod : { "default": mod };
15};
16var __importStar = (this && this.__importStar) || function (mod) {
17 if (mod && mod.__esModule) return mod;
18 var result = {};
19 if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
20 result["default"] = mod;
21 return result;
22};
23Object.defineProperty(exports, "__esModule", { value: true });
24/*
25Copyright (c) 2020 Uber Technologies, Inc.
26
27This source code is licensed under the MIT license found in the
28LICENSE file in the root directory of this source tree.
29*/
30const template_1 = __importDefault(require("@babel/template"));
31const t = __importStar(require("@babel/types"));
32const utils_1 = require("./utils");
33const const_1 = require("./const");
34const ast_1 = require("./ast");
35// forked prettier on a diet
36//@ts-ignore
37const standalone_1 = __importDefault(require("@miksu/prettier/lib/standalone"));
38//@ts-ignore
39const parser_babylon_1 = __importDefault(require("@miksu/prettier/lib/language-js/parser-babylon"));
40const reactImport = template_1.default.ast(`import * as React from 'react';`);
41exports.getAstPropValue = (prop, name, customProps) => {
42 const value = prop.value;
43 switch (prop.type) {
44 case const_1.PropTypes.String:
45 return t.stringLiteral(String(value));
46 case const_1.PropTypes.Boolean:
47 return t.booleanLiteral(Boolean(value));
48 case const_1.PropTypes.Enum:
49 if (!value) {
50 return t.identifier(String(value));
51 }
52 if (!prop.imports) {
53 return t.stringLiteral(String(value));
54 }
55 const [object, property] = String(value).split('.');
56 return t.memberExpression(t.identifier(object), property.includes('-')
57 ? t.stringLiteral(property)
58 : t.identifier(property), property.includes('-') ? true : false);
59 case const_1.PropTypes.Date:
60 return t.newExpression(t.identifier('Date'), value ? [t.stringLiteral(String(value))] : []);
61 case const_1.PropTypes.Ref:
62 return null;
63 case const_1.PropTypes.Object:
64 // need to add this bogus assignment so the value is recognized as an ObjectExpression
65 return template_1.default.ast(`a = ${value}`, { plugins: ['jsx'] })
66 .expression.right;
67 case const_1.PropTypes.Array:
68 case const_1.PropTypes.Number:
69 case const_1.PropTypes.Function:
70 case const_1.PropTypes.ReactNode:
71 const output = template_1.default.ast(String(value), { plugins: ['jsx'] })
72 .expression;
73 // we never expect that user would input a variable as the value
74 // treat it as a string instead
75 if (output.type === 'Identifier') {
76 return t.stringLiteral(output.name);
77 }
78 return output;
79 case const_1.PropTypes.Custom:
80 if (!customProps[name] || !customProps[name].generate) {
81 console.error(`Missing customProps.${name}.generate definition.`);
82 }
83 return customProps[name].generate(value);
84 }
85};
86exports.getAstPropsArray = (props, customProps) => {
87 return Object.entries(props).map(([name, prop]) => {
88 const { value, stateful, defaultValue } = prop;
89 if (stateful)
90 return t.jsxAttribute(t.jsxIdentifier(name), t.jsxExpressionContainer(t.identifier(name)));
91 // When the `defaultValue` is set and `value` is the same as the `defaultValue`
92 // we don't add it to the list of props.
93 // It handles boolean props where `defaultValue` set to true,
94 // and enum props that have a `defaultValue` set to be displayed
95 // in the view correctly (checked checkboxes and selected default value in radio groups)
96 // and not rendered in the component's props.
97 if ((typeof value !== 'boolean' && !value) ||
98 value === defaultValue ||
99 (typeof value === 'boolean' && !value && !defaultValue)) {
100 return null;
101 }
102 const astValue = exports.getAstPropValue(prop, name, customProps);
103 if (!astValue)
104 return null;
105 // shortcut render "isDisabled" vs "isDisabled={true}"
106 if (astValue.type === 'BooleanLiteral' && astValue.value === true) {
107 return t.jsxAttribute(t.jsxIdentifier(name), null);
108 }
109 return t.jsxAttribute(t.jsxIdentifier(name), astValue.type === 'StringLiteral'
110 ? astValue
111 : t.jsxExpressionContainer(astValue));
112 });
113};
114exports.getAstReactHooks = (props, customProps) => {
115 const hooks = [];
116 const buildReactHook = template_1.default(`const [%%name%%, %%setName%%] = React.useState(%%value%%);`);
117 Object.keys(props).forEach(name => {
118 if (props[name].stateful === true) {
119 hooks.push(buildReactHook({
120 name: t.identifier(name),
121 setName: t.identifier(`set${name[0].toUpperCase() + name.slice(1)}`),
122 value: exports.getAstPropValue(props[name], name, customProps),
123 }));
124 }
125 });
126 return hooks;
127};
128exports.getAstImport = (identifiers, source, defaultIdentifier) => {
129 return t.importDeclaration([
130 ...(defaultIdentifier
131 ? [t.importDefaultSpecifier(t.identifier(defaultIdentifier))]
132 : []),
133 ...identifiers.map(identifier => t.importSpecifier(t.identifier(identifier), t.identifier(identifier))),
134 ], t.stringLiteral(source));
135};
136exports.getAstJsxElement = (name, attrs, children) => {
137 const isSelfClosing = children.length === 0;
138 return t.jsxElement(t.jsxOpeningElement(t.jsxIdentifier(name), attrs.filter(attr => !!attr), isSelfClosing), isSelfClosing ? null : t.jsxClosingElement(t.jsxIdentifier(name)), children, true);
139};
140exports.addToImportList = (importList, imports) => {
141 for (const [importFrom, importNames] of Object.entries(imports)) {
142 if (!importList.hasOwnProperty(importFrom)) {
143 importList[importFrom] = {
144 named: [],
145 default: '',
146 };
147 }
148 if (importNames.default) {
149 importList[importFrom].default = importNames.default;
150 }
151 if (importNames.named && importNames.named.length > 0) {
152 if (!importList[importFrom].hasOwnProperty('named')) {
153 importList[importFrom]['named'] = [];
154 }
155 importList[importFrom].named = [
156 ...new Set(importList[importFrom].named.concat(importNames.named)),
157 ];
158 }
159 }
160};
161exports.getAstImports = (importsConfig, providerImports, props) => {
162 // global scoped import that are always displayed
163 const importList = utils_1.clone(importsConfig);
164 // prop level imports (typically enums related) that are displayed
165 // only when the prop is being used
166 Object.values(props).forEach(prop => {
167 if (prop.imports &&
168 prop.value &&
169 prop.value !== '' &&
170 prop.value !== prop.defaultValue) {
171 exports.addToImportList(importList, prop.imports);
172 }
173 });
174 exports.addToImportList(importList, providerImports);
175 return Object.keys(importList).map(from => exports.getAstImport(importList[from].named || [], from, importList[from].default));
176};
177const getChildrenAst = (value) => {
178 return template_1.default.ast(`<>${value}</>`, {
179 plugins: ['jsx'],
180 }).expression.children;
181};
182exports.getAst = (props, componentName, provider, providerValue, importsConfig, customProps) => {
183 const { children } = props, restProps = __rest(props, ["children"]);
184 const buildExport = template_1.default(`export default () => {%%body%%}`);
185 return t.file(t.program([
186 reactImport,
187 ...exports.getAstImports(importsConfig, providerValue ? provider.imports : {}, props),
188 buildExport({
189 body: [
190 ...exports.getAstReactHooks(restProps, customProps),
191 t.returnStatement(provider.generate(providerValue, exports.getAstJsxElement(componentName, exports.getAstPropsArray(restProps, customProps), children && children.value
192 ? getChildrenAst(String(children.value))
193 : []))),
194 ],
195 }),
196 ]), [], []);
197};
198exports.formatAstAndPrint = (ast, printWidth) => {
199 const result = standalone_1.default.__debug.formatAST(ast, {
200 originalText: '',
201 parser: 'babel',
202 printWidth: printWidth ? printWidth : 58,
203 plugins: [parser_babylon_1.default],
204 });
205 return (result.formatted
206 // add a new line before export
207 .replace('export default', `${result.formatted.startsWith('import ') ? '\n' : ''}export default`)
208 // remove newline at the end of file
209 .replace(/[\r\n]+$/, '')
210 // remove ; at the end of file
211 .replace(/[;]+$/, ''));
212};
213exports.formatCode = (code) => {
214 return exports.formatAstAndPrint(ast_1.parse(code));
215};
216exports.getCode = ({ props, componentName, provider, providerValue, importsConfig, customProps, }) => {
217 if (Object.keys(props).length === 0) {
218 return '';
219 }
220 const ast = exports.getAst(props, componentName, provider, providerValue, importsConfig, customProps);
221 return exports.formatAstAndPrint(ast);
222};
223//# sourceMappingURL=code-generator.js.map
\No newline at end of file