|
1 | | -const { babel } = require("@rollup/plugin-babel") |
| 1 | +function addImportDeclarationToProgram(types, program, specifier, imported, source) { |
| 2 | + program.node.body.unshift( |
| 3 | + types.importDeclaration( |
| 4 | + [types.importSpecifier(specifier, types.identifier(imported))], |
| 5 | + types.stringLiteral(source) |
| 6 | + ) |
| 7 | + ) |
| 8 | +} |
2 | 9 |
|
| 10 | +function addStatementToFunction(types, path, statement) { |
| 11 | + if (path.node.body.type === 'BlockStatement') { |
| 12 | + path.node.body.body.unshift(statement) |
| 13 | + } else { |
| 14 | + path.node.body = types.blockStatement([ |
| 15 | + statement, |
| 16 | + types.returnStatement(path.node.body) |
| 17 | + ]) |
| 18 | + } |
| 19 | +} |
3 | 20 |
|
4 | 21 | const functionVisitor = types => (path, { opts }) => { |
5 | 22 | { |
6 | | - const { mode = 'ts' } = opts |
7 | | - |
| 23 | + const { mode = 'vanilla-js' } = opts |
| 24 | + |
8 | 25 | if (mode !== 'ts' && mode !== 'vanilla-js') |
9 | | - throw new Error("Invalid configuration: mode must be 'ts' or 'vanilla-js'.") |
| 26 | + throw new Error("babel-plugin-solid-undestructure error: Invalid configuration - mode must be either 'ts' or 'vanilla-js'.") |
10 | 27 |
|
11 | | - const type = path?.type |
12 | | - |
| 28 | + const type = path.type |
| 29 | + |
13 | 30 | if (mode === 'ts') { |
14 | 31 | if (type !== "ArrowFunctionExpression") return |
15 | | - if (path.parent?.type !== "VariableDeclarator") return |
16 | | - const bindings = path.context?.scope?.bindings |
17 | | - if (!path.parent?.id?.typeAnnotation) return |
| 32 | + if (path.parent.type !== "VariableDeclarator") return |
| 33 | + const bindings = path.context.scope.bindings |
| 34 | + if (!path.parent.id.typeAnnotation) return |
18 | 35 | const typeAnnotation = path.parent.id.typeAnnotation.typeAnnotation |
19 | | - if (typeAnnotation?.type !== "TSTypeReference") return |
20 | | - if (typeAnnotation?.typeName?.type === "Identifier") { |
| 36 | + if (typeAnnotation.type !== "TSTypeReference") return |
| 37 | + if (typeAnnotation.typeName.type === "Identifier") { |
21 | 38 | const typeName = typeAnnotation.typeName.name |
22 | | - const typeBinding = bindings?.[typeName] |
| 39 | + const typeBinding = bindings[typeName] |
23 | 40 | if (!typeBinding) return |
24 | | - const importSpecifier = typeBinding.path?.node |
25 | | - if (importSpecifier?.type !== "ImportSpecifier") return |
26 | | - if (importSpecifier?.imported?.name !== "Component") return |
27 | | - if (typeBinding?.path?.parent?.source?.value !== "solid-js") return |
| 41 | + const importSpecifier = typeBinding.path.node |
| 42 | + if (importSpecifier.type !== "ImportSpecifier") return |
| 43 | + if (importSpecifier.imported.name !== "Component") return |
| 44 | + if (typeBinding.path.parent.source.value !== "solid-js") return |
28 | 45 | } |
29 | | - else if (typeAnnotation?.typeName?.type === "TSQualifiedName") { |
30 | | - if (typeAnnotation.typeName?.right?.name !== "Component") return |
31 | | - const typeQualification = typeAnnotation.typeName?.left |
32 | | - if (typeQualification?.type !== "Identifier") return |
33 | | - const typeQualificationName = typeQualification?.name |
34 | | - const typeQualificationBinding = bindings?.[typeQualificationName] |
| 46 | + else if (typeAnnotation.typeName.type === "TSQualifiedName") { |
| 47 | + if (typeAnnotation.typeName.right.name !== "Component") return |
| 48 | + const typeQualification = typeAnnotation.typeName.left |
| 49 | + if (typeQualification.type !== "Identifier") return |
| 50 | + const typeQualificationName = typeQualification.name |
| 51 | + const typeQualificationBinding = bindings[typeQualificationName] |
35 | 52 | if (!typeQualificationBinding) return |
36 | | - const importSpecifier = typeQualificationBinding?.path?.node |
37 | | - if (importSpecifier?.type !== "ImportDefaultSpecifier") return |
38 | | - if (typeQualificationBinding?.path?.parent?.source?.value !== "solid-js") return |
| 53 | + const importSpecifier = typeQualificationBinding.path.node |
| 54 | + if (importSpecifier.type !== "ImportDefaultSpecifier") return |
| 55 | + if (typeQualificationBinding.path.parent.source.value !== "solid-js") return |
39 | 56 | } |
40 | 57 | else return |
41 | 58 | } |
42 | 59 |
|
43 | 60 | if (mode === 'vanilla-js') { |
44 | | - if (path.parent?.type !== "CallExpression") return |
45 | | - const wrappingFunctionName = path.parent.callee?.name |
46 | | - const bindings = path.context?.scope?.bindings |
47 | | - const wrappingFunctionBinding = bindings?.[wrappingFunctionName] |
| 61 | + if (path.parent.type !== "CallExpression") return |
| 62 | + const wrappingFunctionName = path.parent.callee.name |
| 63 | + const bindings = path.context.scope.bindings |
| 64 | + const wrappingFunctionBinding = bindings[wrappingFunctionName] |
48 | 65 | if (!wrappingFunctionBinding) return |
49 | | - const importSpecifier = wrappingFunctionBinding.path?.node |
50 | | - if (importSpecifier?.type !== "ImportSpecifier") return |
51 | | - if (importSpecifier?.imported?.name !== "component") return |
| 66 | + const importSpecifier = wrappingFunctionBinding.path.node |
| 67 | + if (importSpecifier.type !== "ImportSpecifier") return |
| 68 | + if (importSpecifier.imported.name !== "component") return |
52 | 69 |
|
53 | | - if (wrappingFunctionBinding.path.parent?.source?.value !== 'babel-plugin-solid-undestructure') return |
| 70 | + if (wrappingFunctionBinding.path.parent.source.value !== 'babel-plugin-solid-undestructure') return |
54 | 71 | if (wrappingFunctionBinding.references === 1) wrappingFunctionBinding.path.parentPath.remove() |
55 | | - else wrappingFunctionBinding.references-- |
56 | | - path.parentPath.replaceWith(path) |
| 72 | + else wrappingFunctionBinding.references-- |
| 73 | + path.parentPath.replaceWith(path) |
57 | 74 | } |
58 | 75 | } |
59 | 76 |
|
60 | | - const firstParam = path.node?.params?.[0] |
61 | | - if (!firstParam || firstParam.type !== "ObjectPattern") return |
| 77 | + let firstParam = path.node.params[0] |
| 78 | + if ( |
| 79 | + !firstParam |
| 80 | + || ( |
| 81 | + firstParam.type !== "ObjectPattern" |
| 82 | + && firstParam.type !== "AssignmentPattern" |
| 83 | + ) |
| 84 | + ) return |
| 85 | + |
| 86 | + let defaultPropsWhole = types.objectExpression([]) |
| 87 | + if (firstParam.type == "AssignmentPattern") { |
| 88 | + defaultPropsWhole = firstParam.right |
| 89 | + firstParam = firstParam.left |
| 90 | + } |
62 | 91 |
|
63 | | - const program = path?.findParent(path => path.isProgram()) |
64 | | - const newPropsIdentifier = |
65 | | - program?.scope?.generateUidIdentifier("props") |
| 92 | + const program = path.findParent(path => path.isProgram()) |
| 93 | + const newPropsIdentifier = program.scope.generateUidIdentifier("props") |
66 | 94 |
|
67 | | - const propsDestructredProperties = firstParam?.properties |
68 | | - const componentScopeBindings = path.scope?.bindings |
| 95 | + const propsDestructredProperties = firstParam.properties |
| 96 | + const componentScopeBindings = path.scope.bindings |
69 | 97 |
|
70 | | - for (const DestructredProperty of propsDestructredProperties) |
71 | | - if (!DestructredProperty?.value?.name) return |
| 98 | + let defaultPropsObject |
72 | 99 |
|
73 | 100 | for (const DestructredProperty of propsDestructredProperties) { |
| 101 | + if (DestructredProperty.type === "RestElement") throw new Error("babel-plugin-solid-undestructure error: Rest elements are not supported.") |
| 102 | + if ( |
| 103 | + ( |
| 104 | + // Nested destructuring |
| 105 | + DestructredProperty.value.type !== "Identifier" |
| 106 | + && DestructredProperty.value.type !== "AssignmentPattern" |
| 107 | + ) |
| 108 | + || ( |
| 109 | + // Nested destructuring + default value |
| 110 | + DestructredProperty.value.type !== "Identifier" |
| 111 | + && DestructredProperty.value.left.type !== "Identifier" |
| 112 | + ) |
| 113 | + ) |
| 114 | + throw new Error("babel-plugin-solid-undestructure error: Nested destructuring is not supported.") |
| 115 | + |
| 116 | + // Handle default props |
| 117 | + if (DestructredProperty.value.type === "AssignmentPattern") { |
| 118 | + if (!defaultPropsObject) { |
| 119 | + // Look for `mergeProps` import |
| 120 | + let mergePropsUniqueName |
| 121 | + program.traverse({ |
| 122 | + ImportDeclaration(path) { |
| 123 | + if (mergePropsUniqueName || path.node.source.value !== "solid-js") return |
| 124 | + for (const specifier of path.node.specifiers) |
| 125 | + if ( |
| 126 | + specifier.imported |
| 127 | + && specifier.imported.name === "mergeProps" |
| 128 | + && specifier.local.unique |
| 129 | + ) { |
| 130 | + mergePropsUniqueName = specifier.local |
| 131 | + return |
| 132 | + } |
| 133 | + } |
| 134 | + }) |
| 135 | + |
| 136 | + // If not found, create one |
| 137 | + if (!mergePropsUniqueName) { |
| 138 | + mergePropsUniqueName = program.scope.generateUidIdentifier("mergeProps") |
| 139 | + mergePropsUniqueName.unique = true |
| 140 | + addImportDeclarationToProgram(types, program, mergePropsUniqueName, "mergeProps", "solid-js") |
| 141 | + } |
| 142 | + |
| 143 | + defaultPropsObject = types.objectExpression([types.objectProperty(DestructredProperty.value.left ,DestructredProperty.value.right)]) |
| 144 | + const callExpression = types.callExpression(mergePropsUniqueName, [defaultPropsWhole, defaultPropsObject, newPropsIdentifier]) |
| 145 | + const assignmentStatement = types.expressionStatement(types.assignmentExpression("=", newPropsIdentifier, callExpression)) |
| 146 | + addStatementToFunction(types, path, assignmentStatement) |
| 147 | + } |
| 148 | + |
| 149 | + else defaultPropsObject.properties.push(types.objectProperty(DestructredProperty.value.left ,DestructredProperty.value.right)) |
| 150 | + } |
| 151 | + |
74 | 152 | const DestructredKeyIdentifier = DestructredProperty.key |
75 | 153 | const undestructuredPropExpression = |
76 | | - types.memberExpression(newPropsIdentifier, DestructredKeyIdentifier) |
| 154 | + types.memberExpression(newPropsIdentifier, DestructredKeyIdentifier) |
77 | 155 |
|
78 | | - const DestructredName = DestructredProperty.value?.name |
| 156 | + const DestructredName = DestructredProperty.value.name || DestructredProperty.value.left.name |
79 | 157 |
|
80 | | - const { referencePaths, constantViolations } = componentScopeBindings?.[DestructredName] |
| 158 | + path.scope.crawl() |
| 159 | + const componentScopeBindings = path.scope.bindings |
| 160 | + const { referencePaths, constantViolations } = componentScopeBindings[DestructredName] |
81 | 161 |
|
82 | 162 | for (const referencePath of referencePaths) |
83 | | - referencePath?.replaceWith(undestructuredPropExpression) |
| 163 | + referencePath.replaceWith(undestructuredPropExpression) |
84 | 164 |
|
85 | 165 | for (const constantViolation of constantViolations) |
86 | 166 | constantViolation.node && (constantViolation.node.left = undestructuredPropExpression) |
87 | 167 | } |
88 | 168 |
|
89 | 169 | path.node.params[0] = newPropsIdentifier |
90 | 170 | } |
91 | | - |
92 | | - |
93 | | -module.exports = function babelPluginUndestructure ({ types }) { |
| 171 | + |
| 172 | + |
| 173 | + module.exports = function babelPluginUndestructure ({ types }) { |
94 | 174 | const visitor = { |
95 | 175 | FunctionDeclaration: functionVisitor(types), |
96 | 176 | FunctionExpression: functionVisitor(types), |
|
0 commit comments