Skip to content

Commit

Permalink
feat(babel-helpers): add objectSpread
Browse files Browse the repository at this point in the history
  • Loading branch information
pionxzh committed Sep 6, 2023
1 parent c540d39 commit 6000c0f
Show file tree
Hide file tree
Showing 2 changed files with 225 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import transform from '../../babel-helpers/objectSpread'
import { defineInlineTest } from '../test-utils'

const inlineTest = defineInlineTest(transform)

inlineTest('objectSpread',
`
var _objectSpread2 = require("@babel/runtime/helpers/objectSpread2");
a = _objectSpread2({}, y);
b = _objectSpread2.default({}, y);
c = (0, _objectSpread2)({}, y);
d = (0, _objectSpread2.default)({}, y);
`,
`
a = {
...y
};
b = {
...y
};
c = {
...y
};
d = {
...y
};
`,
)

inlineTest('objectSpread - esm',
`
import _objectSpread2 from "@babel/runtime/helpers/esm/objectSpread2";
a = _objectSpread2({}, y);
b = _objectSpread2.default({}, y);
c = (0, _objectSpread2)({}, y);
d = (0, _objectSpread2.default)({}, y);
`,
`
a = {
...y
};
b = {
...y
};
c = {
...y
};
d = {
...y
};
`,
)

inlineTest('objectSpread - cases',
`
import _objectSpread2 from "@babel/runtime/helpers/esm/objectSpread";
a = _objectSpread2({}, y);
b = _objectSpread2({ x }, y);
c = _objectSpread2({ x: x }, y);
d = _objectSpread2({ x: z }, { y: 'bar'});
e = _objectSpread2({}, { get y() {} });
f = _objectSpread2({ x }, { y: _objectSpread2({}, z) });
g = _objectSpread2(
_objectSpread2(
_objectSpread2(
{ a },
b
),
{},
{ c },
d
),
{},
{ e }
);
`,
`
a = {
...y
};
b = {
x,
...y
};
c = {
x: x,
...y
};
d = {
x: z,
y: 'bar'
};
e = {
get y() {}
};
f = {
x,
y: {
...z
}
};
g = {
a,
...b,
c,
...d,
e
};
`,
)
111 changes: 111 additions & 0 deletions packages/unminify/src/transformations/babel-helpers/objectSpread.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { findModuleSource } from '../../utils/findModuleSource'
import { removeDeclarationIfUnused, removeDefaultImportIfUnused } from '../../utils/removeDeclarationIfUnused'
import wrap from '../../wrapAstTransformation'
import type { ASTTransformation } from '../../wrapAstTransformation'
import type { Identifier } from '@babel/types'
import type { ASTPath, CallExpression, ImportDefaultSpecifier, JSCodeshift, ObjectExpression } from 'jscodeshift'

/**
* Restore object spread syntax from `@babel/runtime/helpers/objectSpread2` helper.
*/
export const transformAST: ASTTransformation = (context) => {
const { root, j } = context

/**
* `objectSpread2` was introduced in Babel v7.5.0
*/
const moduleName = '@babel/runtime/helpers/objectSpread2'
const moduleEsmName = '@babel/runtime/helpers/esm/objectSpread2'
const fallbackModuleName = '@babel/runtime/helpers/objectSpread'
const fallbackModuleEsmName = '@babel/runtime/helpers/esm/objectSpread'
const moduleSource = findModuleSource(j, root, moduleName)
|| findModuleSource(j, root, moduleEsmName)
|| findModuleSource(j, root, fallbackModuleName)
|| findModuleSource(j, root, fallbackModuleEsmName)

if (moduleSource) {
const isImport = j.ImportDeclaration.check(moduleSource)
const moduleVariableName = isImport
? ((moduleSource.specifiers![0] as ImportDefaultSpecifier).local as Identifier).name
: (moduleSource.id as Identifier).name

// objectSpread({}, foo)
// objectSpread.default({ x }, y)
root
.find(j.CallExpression, {
callee: (callee: CallExpression['callee']) => {
return (
j.Identifier.check(callee)
&& callee.name === moduleVariableName
)
|| (
j.MemberExpression.check(callee)
&& j.Identifier.check(callee.object)
&& callee.object.name === moduleVariableName
&& j.Identifier.check(callee.property)
&& callee.property.name === 'default'
)
},
})
.paths()
.reverse()
.forEach((path) => {
handleSpread(j, path, isImport, moduleVariableName)
})

// (0, objectSpread)([...])
// (0, objectSpread.default)([...])
root
.find(j.CallExpression, {
callee: {
type: 'SequenceExpression',
expressions: [
{ type: 'Literal', value: 0 },
(expression: any) => {
return (
j.Identifier.check(expression)
&& expression.name === moduleVariableName
)
|| (
j.MemberExpression.check(expression)
&& j.Identifier.check(expression.object)
&& expression.object.name === moduleVariableName
&& j.Identifier.check(expression.property)
&& expression.property.name === 'default'
)
},
],
},
})
.paths()
.reverse()
.forEach((path) => {
handleSpread(j, path, isImport, moduleVariableName)
})
}
}

function handleSpread(j: JSCodeshift, path: ASTPath<CallExpression>, isImport: boolean, moduleVariableName: string) {
const properties: ObjectExpression['properties'] = []

for (const arg of path.node.arguments) {
if (j.ObjectExpression.check(arg)) {
properties.push(...arg.properties)
}
else if (j.SpreadElement.check(arg)) {
properties.push(arg)
}
else {
properties.push(j.spreadElement(arg))
}
}

const spreadObject = j.objectExpression(properties)
path.replace(spreadObject)

isImport
? removeDefaultImportIfUnused(j, path, moduleVariableName)
: removeDeclarationIfUnused(j, path, moduleVariableName)
}

export default wrap(transformAST)

0 comments on commit 6000c0f

Please sign in to comment.