@patternfly/ast-helpers 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/acorn.js ADDED
@@ -0,0 +1,17 @@
1
+ const { Parser } = require('acorn');
2
+ const jsx = require('acorn-jsx');
3
+ const staticClassFeatures = require('acorn-static-class-features');
4
+ const classFields = require('acorn-class-fields');
5
+ const typescript = require('./acorn-typescript');
6
+
7
+ const jsxParser = Parser.extend(typescript, jsx(), classFields, staticClassFeatures);
8
+
9
+ module.exports = {
10
+ parse(code) {
11
+ return jsxParser.parse(code, {
12
+ ecmaVersion: 2018,
13
+ sourceType: 'module',
14
+ allowReturnOutsideFunction: true
15
+ });
16
+ }
17
+ };
package/index.js ADDED
@@ -0,0 +1,8 @@
1
+ const { parse } = require('./acorn');
2
+ const { convertToReactComponent, convertToJSX } = require('./stringify');
3
+
4
+ module.exports = {
5
+ parse,
6
+ convertToReactComponent,
7
+ convertToJSX
8
+ };
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@patternfly/ast-helpers",
3
+ "description": "Acorn AST helpers for working with live code",
4
+ "version": "0.0.4",
5
+ "author": "Red Hat",
6
+ "license": "MIT",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "main": "index.js",
11
+ "dependencies": {
12
+ "acorn": "^8.4.1",
13
+ "acorn-class-fields": "^1.0.0",
14
+ "acorn-jsx": "^5.3.2",
15
+ "acorn-static-class-features": "^1.0.0",
16
+ "astring": "^1.7.5"
17
+ },
18
+ "gitHead": "4ddc61fd2d7abee3d2ddff3bc0c58a31062e48c9"
19
+ }
package/stringify.js ADDED
@@ -0,0 +1,330 @@
1
+ const { generate, baseGenerator } = require('astring');
2
+ const { parse } = require('./acorn');
3
+
4
+ const commonGenerator = {
5
+ ...baseGenerator,
6
+ // Strip types
7
+ TSTypeAnnotation() {},
8
+ TSTypeReference() {},
9
+ TSLiteralType() {},
10
+ TSTupleType() {},
11
+ TSOptionalType() {},
12
+ TSRestType() {},
13
+ TSArrayType() {},
14
+ TSIndexedAccessType() {},
15
+ TSFunctionType() {},
16
+ TSParenthesizedType() {},
17
+ TSUnionType() {},
18
+ TSIntersectionType() {},
19
+ TSConditionalType() {},
20
+ TSInferType() {},
21
+ TSImportType() {},
22
+ TSQualifiedName() {},
23
+ TSConstructorType() {},
24
+ TSConstructSignatureDeclaration() {},
25
+ TSTypeLiteral() {},
26
+ TSTypeAliasDeclaration() {},
27
+ TSInterfaceBody() {},
28
+ TSInterfaceDeclaration() {},
29
+ TSExpressionWithTypeArguments() {},
30
+ TSTypeParameter() {},
31
+ TSTypeParameterDeclaration() {},
32
+ TSTypeParameterInstantiation() {},
33
+ TSMethodSignature() {},
34
+ TSPropertySignature() {},
35
+ TSIndexSignature() {},
36
+ TSMappedType() {},
37
+ TSTypeParameter() {},
38
+ TSAsExpression() {},
39
+ TSNonNullExpression() {},
40
+ // Class features
41
+ FieldDefinition(node, state) {
42
+ this[node.key.type](node.key, state);
43
+ state.write(' = ');
44
+ this[node.value.type](node.value, state);
45
+ state.write(';');
46
+ },
47
+ PropertyDefinition(node, state) {
48
+ this.FieldDefinition(node, state);
49
+ },
50
+ };
51
+
52
+ // Stringify ES2017 TSX w/class members -> ES2017 w/o imports/exports so it can be `eval`ed
53
+ const es2017Generator = {
54
+ ...commonGenerator,
55
+ JSXElement(node, state) {
56
+ state.write('React.createElement(');
57
+ this.JSXOpeningElement(node.openingElement, state);
58
+ for (var i = 0; i < node.children.length; i++) {
59
+ var child = node.children[i];
60
+ if (child.type !== 'JSXText') {
61
+ state.write(',');
62
+ }
63
+ this[child.type](child, state);
64
+ }
65
+ state.write(')');
66
+ },
67
+ JSXOpeningElement(node, state) {
68
+ this[node.name.type](node.name, state, node.name.name && node.name.name[0] === node.name.name[0].toLowerCase());
69
+ state.write(',{');
70
+ for (var i = 0; i < node.attributes.length; i++) {
71
+ var attr = node.attributes[i];
72
+ this[attr.type](attr, state);
73
+ if (i !== node.attributes.length - 1) {
74
+ state.write(',');
75
+ }
76
+ }
77
+ state.write('}');
78
+ },
79
+ JSXIdentifier(node, state, escape) {
80
+ if (escape) {
81
+ state.write(JSON.stringify(node.name));
82
+ }
83
+ else {
84
+ state.write(node.name);
85
+ }
86
+ },
87
+ JSXMemberExpression(node, state) {
88
+ this[node.object.type](node.object, state);
89
+ state.write('.');
90
+ this[node.property.type](node.property, state);
91
+ },
92
+ JSXAttribute(node, state) {
93
+ this[node.name.type](node.name, state, true);
94
+ state.write(':');
95
+ if (node.value) {
96
+ this[node.value.type](node.value, state);
97
+ }
98
+ else {
99
+ state.write('true');
100
+ }
101
+ },
102
+ JSXExpressionContainer(node, state) {
103
+ this[node.expression.type](node.expression, state);
104
+ },
105
+ JSXText(node, state) {
106
+ if (node.value.trim() === '') {
107
+ node.value = null;
108
+ }
109
+ if (node.value) {
110
+ state.write(',"');
111
+ state.write(node.value.replace(/"/g, '\\"').replace(/\n\s*/g, ' '));
112
+ state.write('"');
113
+ }
114
+ },
115
+ JSXSpreadAttribute(node, state) {
116
+ state.write('...(');
117
+ if (node.argument.type === 'LogicalExpression') {
118
+ this[node.argument.left.type](node.argument.left, state);
119
+ state.write(' ');
120
+ state.write(node.argument.operator);
121
+ state.write(' ');
122
+ this[node.argument.right.type](node.argument.right, state);
123
+ }
124
+ else {
125
+ this[node.argument.type](node.argument, state);
126
+ }
127
+ state.write(')');
128
+ },
129
+ JSXFragment(node, state) {
130
+ this.JSXElement({
131
+ openingElement: {
132
+ attributes: [],
133
+ name: {
134
+ type: 'JSXIdentifier',
135
+ name: 'React.Fragment'
136
+ }
137
+ },
138
+ children: node.children
139
+ }, state);
140
+ },
141
+ JSXEmptyExpression(_node, state) {
142
+ state.write('null');
143
+ },
144
+ };
145
+
146
+ // Stringify ES2017 TSX w/class members -> ES2017 JSX w/class members
147
+ const es2017GeneratorJSX = {
148
+ ...commonGenerator,
149
+ // <div></div>
150
+ JSXElement(node, state) {
151
+ state.write('<');
152
+ this[node.openingElement.type](node.openingElement, state);
153
+ if (node.closingElement) {
154
+ state.write('>');
155
+ for (var i = 0; i < node.children.length; i++) {
156
+ var child = node.children[i];
157
+ this[child.type](child, state);
158
+ }
159
+ state.write('</');
160
+ this[node.closingElement.type](node.closingElement, state);
161
+ state.write('>');
162
+ } else {
163
+ state.write(' />');
164
+ }
165
+ },
166
+ // <div>
167
+ JSXOpeningElement(node, state) {
168
+ this[node.name.type](node.name, state);
169
+ for (var i = 0; i < node.attributes.length; i++) {
170
+ var attr = node.attributes[i];
171
+ this[attr.type](attr, state);
172
+ }
173
+ },
174
+ // </div>
175
+ JSXClosingElement(node, state) {
176
+ this[node.name.type](node.name, state);
177
+ },
178
+ // div
179
+ JSXIdentifier(node, state) {
180
+ state.write(node.name);
181
+ },
182
+ // Member.Expression
183
+ JSXMemberExpression(node, state) {
184
+ this[node.object.type](node.object, state);
185
+ state.write('.');
186
+ this[node.property.type](node.property, state);
187
+ },
188
+ // attr="something"
189
+ JSXAttribute(node, state) {
190
+ state.write(' ');
191
+ this[node.name.type](node.name, state);
192
+ if (node.value) {
193
+ state.write('=');
194
+ this[node.value.type](node.value, state);
195
+ }
196
+ },
197
+ // namespaced:attr="something"
198
+ JSXNamespacedName(node, state) {
199
+ this[node.namespace.type](node.namespace, state);
200
+ state.write(':');
201
+ this[node.name.type](node.name, state);
202
+ },
203
+ // {expression}
204
+ JSXExpressionContainer(node, state) {
205
+ state.write('{');
206
+ this[node.expression.type](node.expression, state);
207
+ state.write('}');
208
+ },
209
+ // anything between JSX nodes
210
+ JSXText(node, state) {
211
+ state.write(node.value);
212
+ },
213
+ // {...props}
214
+ JSXSpreadAttribute(node, state) {
215
+ state.write('...(');
216
+ if (node.argument.type === 'LogicalExpression') {
217
+ this[node.argument.left.type](node.argument.left, state);
218
+ state.write(' ');
219
+ state.write(node.argument.operator);
220
+ state.write(' ');
221
+ this[node.argument.right.type](node.argument.right, state);
222
+ }
223
+ else {
224
+ this[node.argument.type](node.argument, state);
225
+ }
226
+ state.write(')');
227
+ },
228
+ // <></>
229
+ JSXFragment(node, state) {
230
+ this.JSXElement({
231
+ openingElement: {
232
+ attributes: [],
233
+ name: {
234
+ type: 'JSXIdentifier',
235
+ name: 'React.Fragment'
236
+ }
237
+ },
238
+ children: node.children
239
+ }, state);
240
+ },
241
+ // {} (not very kosher)
242
+ JSXEmptyExpression(_node, state) {
243
+ state.write('{}');
244
+ },
245
+ };
246
+
247
+ // ES2017 TSX w/class members -> ES2017 React Component
248
+ function convertToReactComponent(code) {
249
+ const ast = parse(code);
250
+
251
+ // Modify AST for function creation
252
+ ast.body = ast.body.filter(node => !['ImportDeclaration', 'ExportAllDeclaration'].includes(node.type));
253
+ for (let i = 0; i < ast.body.length; i++) {
254
+ if (['ExportNamedDeclaration', 'ExportDefaultDeclaration'].includes(ast.body[i].type)) {
255
+ // Replace exports
256
+ ast.body[i] = ast.body[i].declaration;
257
+ }
258
+ }
259
+
260
+ // Create Function that returns React Component
261
+ // Create React component by returning last member of body
262
+ let lastStatement = ast.body[ast.body.length - 1];
263
+ // Convert `const Example` to `Example`
264
+ if (lastStatement.type === 'VariableDeclaration') {
265
+ const { declarations } = lastStatement;
266
+ if (declarations.length !== 1) {
267
+ throw new Error('The last example variable declaration must be a single expression.');
268
+ }
269
+ const declaration = declarations[0];
270
+ lastStatement = {
271
+ type: 'ExpressionStatement',
272
+ expression: {
273
+ type: 'AssignmentExpression',
274
+ operator: '=',
275
+ left: declaration.id,
276
+ right: declaration.init
277
+ }
278
+ };
279
+ }
280
+ // Convert `<InlineJSX />` or `Example = () => <InlineJSX />`
281
+ // to `function PreviewComponent() { return <InlineJSX />; }`
282
+ if (lastStatement.type === 'ExpressionStatement' && lastStatement.expression.type === 'JSXElement') {
283
+ ast.body = [{
284
+ type: 'ReturnStatement',
285
+ argument: {
286
+ type: 'FunctionDeclaration',
287
+ id: {
288
+ type: 'Identifier',
289
+ name: 'PreviewComponent'
290
+ },
291
+ body: {
292
+ type: 'BlockStatement',
293
+ body: [
294
+ ...ast.body.slice(0, ast.body.length - 1),
295
+ {
296
+ type: 'ReturnStatement',
297
+ argument: lastStatement
298
+ }
299
+ ]
300
+ }
301
+ }
302
+ }];
303
+ } else {
304
+ ast.body = [
305
+ ...ast.body.slice(0, ast.body.length - 1),
306
+ {
307
+ type: 'ReturnStatement',
308
+ argument: lastStatement
309
+ }
310
+ ];
311
+ }
312
+
313
+ //console.log(ast)
314
+ code = generate(ast, { generator: es2017Generator }).trim();
315
+ //console.log(code)
316
+ return { code, hasTS: ast.sourceType === 'ts' };
317
+ }
318
+
319
+ // ES2017 TSX w/class members -> ES2017 JSX
320
+ function convertToJSX(code) {
321
+ const ast = parse(code);
322
+ code = generate(ast, { generator: es2017GeneratorJSX }).trim();
323
+ return { code, hasTS: false };
324
+ }
325
+
326
+ module.exports = {
327
+ convertToReactComponent,
328
+ convertToJSX
329
+ };
330
+