Skip to content

Commit

Permalink
feat: extract @unminify/ast-utils
Browse files Browse the repository at this point in the history
  • Loading branch information
pionxzh committed Aug 5, 2023
1 parent 53bd538 commit 9c5fe11
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 55 deletions.
48 changes: 48 additions & 0 deletions packages/ast-utils/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "@unminify/ast-utils",
"type": "module",
"version": "0.0.1",
"packageManager": "[email protected]",
"description": "🔪📦 Unminify and beautify bundled code",
"author": "Pionxzh",
"license": "MIT",
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.cjs",
"import": "./dist/index.js"
}
},
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist",
"package.json"
],
"engines": {
"node": ">=16.0.0"
},
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts --sourcemap --clean",
"test": "vitest run --globals",
"test:update": "vitest run --update --globals",
"test:watch": "vitest watch --globals",
"test:type": "tsc --noEmit",
"lint": "eslint src",
"lint:fix": "eslint src --fix"
},
"dependencies": {
"@babel/types": "^7.20.7",
"eslint": "^8.32.0",
"jscodeshift": "^0.14.0"
},
"devDependencies": {
"@types/jscodeshift": "^0.11.6",
"@types/yargs": "^17.0.20",
"tsup": "^6.5.0",
"typescript": "^4.9.4",
"vitest": "^0.28.3"
}
}
5 changes: 5 additions & 0 deletions packages/ast-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { isIIFE } from './isIIFE'
export { isTopLevel } from './isTopLevel'
export { renameFunctionParameters } from './renameFunctionParameters'
export { splitVariableDeclarators } from './splitVariableDeclarators'
export { pruneComments } from './pruneComments'
10 changes: 10 additions & 0 deletions packages/ast-utils/src/isIIFE.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { ExpressionStatement, Statement } from 'jscodeshift'

export function isIIFE(node: Statement): node is ExpressionStatement {
if (node.type !== 'ExpressionStatement') return false
const expression = (node as ExpressionStatement).expression
if (expression.type !== 'CallExpression') return false
const callee = expression.callee
return callee.type === 'FunctionExpression'
|| callee.type === 'ArrowFunctionExpression'
}
File renamed without changes.
6 changes: 6 additions & 0 deletions packages/ast-utils/src/pruneComments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { Collection, JSCodeshift } from 'jscodeshift'

export function pruneComments(j: JSCodeshift, collection: Collection): void {
// @ts-expect-error - Comment type is wrong
collection.find(j.Comment).forEach(path => path.prune())
}
26 changes: 26 additions & 0 deletions packages/ast-utils/src/renameFunctionParameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { ArrowFunctionExpression, FunctionExpression, JSCodeshift } from 'jscodeshift'

export function renameFunctionParameters(j: JSCodeshift, node: FunctionExpression | ArrowFunctionExpression, parameters: string[]): void {
node.params.forEach((param, index) => {
if (param.type === 'Identifier') {
const oldName = param.name
const newName = parameters[index]

// Only get the immediate function scope
const functionScope = j(node).closestScope().get()

// Check if the name is in the current scope and rename it
if (functionScope.scope.getBindings()[oldName]) {
j(functionScope)
.find(j.Identifier, { name: oldName })
.forEach((path) => {
// Exclude MemberExpression properties
if (!(path.parent.node.type === 'MemberExpression' && path.parent.node.property === path.node)
&& path.scope.node === functionScope.node) {
path.node.name = newName
}
})
}
}
})
}
30 changes: 30 additions & 0 deletions packages/ast-utils/src/splitVariableDeclarators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { Collection, JSCodeshift } from 'jscodeshift'

/**
* ```js
* var a = 1, b = true, c = func(d)
* ->
* var a = 1
* var b = true
* var c = func(d)
* ```
*/
export function splitVariableDeclarators(j: JSCodeshift, collection: Collection) {
collection
.find(j.VariableDeclaration, {
declarations: [
{
type: 'VariableDeclarator',
id: { type: 'Identifier' },
},
],
})
.filter((path) => {
if (path.parent?.node.type === 'ForStatement') return false
return path.node.declarations.length > 1
})
.forEach((p) => {
const { kind, declarations } = p.node
j(p).replaceWith(declarations.map(d => j.variableDeclaration(kind, [d])))
})
}
27 changes: 27 additions & 0 deletions packages/ast-utils/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"lib": ["ESNext"],
"moduleResolution": "Node",
"strict": true,
"outDir": "dist",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true,
"useDefineForClassFields": true
},
"include": [
"src"
],
"exclude": [
"node_modules",
"dist"
]
}
3 changes: 2 additions & 1 deletion packages/unminify/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "unminify",
"name": "@unminify/kit",
"type": "module",
"version": "0.0.1",
"packageManager": "[email protected]",
Expand Down Expand Up @@ -38,6 +38,7 @@
"@babel/core": "^7.20.12",
"@babel/preset-env": "^7.20.2",
"@babel/types": "^7.20.7",
"@unminify/ast-utils": "workspace:*",
"eslint": "^8.32.0",
"fs-extra": "^11.1.0",
"globby": "^11.1.0",
Expand Down
5 changes: 4 additions & 1 deletion packages/unminify/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true,
"useDefineForClassFields": true
"useDefineForClassFields": true,
"paths": {
"@unminify/ast-utils": ["../ast-utils/src/index.ts"]
}
},
"include": [
"src",
Expand Down
1 change: 1 addition & 0 deletions packages/unpacker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"lint:fix": "eslint src --fix"
},
"dependencies": {
"@unminify/ast-utils": "workspace:*",
"ast-types": "^0.14.2",
"jscodeshift": "^0.14.0",
"picocolors": "^1.0.0",
Expand Down
81 changes: 31 additions & 50 deletions packages/unpacker/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,6 @@
import type { ASTPath, ArrowFunctionExpression, ClassDeclaration, Collection, ExpressionStatement, FunctionDeclaration, FunctionExpression, JSCodeshift, Node, Statement, VariableDeclaration } from 'jscodeshift'
import prettier from 'prettier/standalone'
import babelParser from 'prettier/parser-babel'

export function isTopLevel(j: JSCodeshift, node: ASTPath<Node>): boolean {
return j.Program.check(node.parentPath.node)
}

export function pruneComments(j: JSCodeshift, collection: Collection<any>): void {
// @ts-expect-error - Comment type is wrong
collection.find(j.Comment).forEach(path => path.prune())
}

export function renameFunctionParameters(j: JSCodeshift, node: FunctionExpression | ArrowFunctionExpression, parameters: string[]): void {
node.params.forEach((param, index) => {
if (param.type === 'Identifier') {
const oldName = param.name
const newName = parameters[index]

// Only get the immediate function scope
const functionScope = j(node).closestScope().get()

// Check if the name is in the current scope and rename it
if (functionScope.scope.getBindings()[oldName]) {
j(functionScope)
.find(j.Identifier, { name: oldName })
.forEach((path) => {
// Exclude MemberExpression properties
if (!(path.parent.node.type === 'MemberExpression' && path.parent.node.property === path.node)
&& path.scope.node === functionScope.node) {
path.node.name = newName
}
})
}
}
})
}
import prettier from 'prettier/standalone'
import type { Collection, JSCodeshift } from 'jscodeshift'

export function wrapDeclarationWithExport(
j: JSCodeshift,
Expand All @@ -55,7 +21,7 @@ export function wrapDeclarationWithExport(
return
}
const declarationPath = declarations[0].parent?.parent
const declarationNode = declarationPath.value
const declarationNode = declarationPath?.value
if (!declarationNode) {
console.warn('Failed to locate declaration node:', declarationName)
return
Expand All @@ -68,23 +34,38 @@ export function wrapDeclarationWithExport(
&& !j.FunctionDeclaration.check(declarationNode)
&& !j.ClassDeclaration.check(declarationNode)) {
console.warn(`Declaration is not a variable, function or class: ${declarationName}, the type is ${declarationNode.type}`)
console.warn(j(declarationPath).toSource())
return
}

const exportDeclaration = exportName === 'default'
? j.exportDefaultDeclaration(declarationNode)
: j.exportNamedDeclaration(declarationNode)

j(declarationPath).replaceWith(exportDeclaration)
}
if (j.VariableDeclaration.check(declarationNode) && declarationNode.declarations.length > 1) {
// special case for multiple variable declarators
// e.g. `var a = 1, b = 2`
const declarators = declarationNode.declarations
const exportDeclarator = declarators.find((declarator) => {
return j.VariableDeclarator.check(declarator) && j.Identifier.check(declarator.id) && declarator.id.name === declarationName
})
if (!exportDeclarator) {
console.warn(`Failed to locate export variable declarator: ${declarationName}`)
return
}
const newVariableDeclaration = j.variableDeclaration(declarationNode.kind, [exportDeclarator])
const newDeclaration = exportName === 'default'
? j.exportDefaultDeclaration(newVariableDeclaration)
: j.exportNamedDeclaration(newVariableDeclaration)
const filteredDeclaration = j.variableDeclaration(
declarationNode.kind,
declarators.filter(declarator => declarator !== exportDeclarator),
)
j(declarationPath).replaceWith(filteredDeclaration).insertBefore(newDeclaration)
}
else {
const exportDeclaration = exportName === 'default'
? j.exportDefaultDeclaration(declarationNode)
: j.exportNamedDeclaration(declarationNode)

export function isIIFE(node: Statement): node is ExpressionStatement {
if (node.type !== 'ExpressionStatement') return false
const expression = (node as ExpressionStatement).expression
if (expression.type !== 'CallExpression') return false
const callee = expression.callee
return callee.type === 'FunctionExpression'
|| callee.type === 'ArrowFunctionExpression'
j(declarationPath).replaceWith(exportDeclaration)
}
}

export function prettierFormat(code: string) {
Expand Down
8 changes: 5 additions & 3 deletions packages/unpacker/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true,
"useDefineForClassFields": true
"useDefineForClassFields": true,
"paths": {
"@unminify/ast-utils": ["../ast-utils/src/index.ts"]
}
},
"include": [
"src",
"lebab.d.ts"
"src"
],
"exclude": [
"node_modules",
Expand Down

0 comments on commit 9c5fe11

Please sign in to comment.