diff --git a/apps/docs/src/concepts/unminify.md b/apps/docs/src/concepts/unminify.md index 5146177e..0b5f0e24 100644 --- a/apps/docs/src/concepts/unminify.md +++ b/apps/docs/src/concepts/unminify.md @@ -88,7 +88,19 @@ if (x) { } // [!code --] if (x) { -} else if (y) {} // [!code ++] +} else if (y) { // [!code ++] +} else {} // [!code ++] +``` + +```js +if (x) { // [!code --] + if (y) {} // [!code --] +} else { // [!code --] +} // [!code --] + +if (!x) { // [!code ++] +} else if (y) { // [!code ++] +} else {} // [!code ++] ``` ## merge-strings diff --git a/packages/webcrack/src/unminify/test/merge-else-if.test.ts b/packages/webcrack/src/unminify/test/merge-else-if.test.ts index 1ad7e714..d13c7c77 100644 --- a/packages/webcrack/src/unminify/test/merge-else-if.test.ts +++ b/packages/webcrack/src/unminify/test/merge-else-if.test.ts @@ -4,12 +4,64 @@ import mergeElseIf from '../transforms/merge-else-if'; const expectJS = testTransform(mergeElseIf); -test('merge', () => +test('merge', () => { expectJS(` if (x) { } else { if (y) {} - }`).toMatchInlineSnapshot('if (x) {} else if (y) {}')); + }`).toMatchInlineSnapshot('if (x) {} else if (y) {}'); + + expectJS(` + if (!cond) { + if (cond2 !== 5) { + if (cond3) { + console.log("branch 3"); + } else { + console.log("branch 4"); + } + } else { + console.log("branch 2"); + } + } else { + console.log("branch 1"); + }`).toMatchInlineSnapshot(` + if (!!cond) { + console.log("branch 1"); + } else if (cond2 === 5) { + console.log("branch 2"); + } else if (cond3) { + console.log("branch 3"); + } else { + console.log("branch 4"); + } + `); + + expectJS(` + if (!cond) { + if (cond2) { + console.log("branch 2"); + } + }`).toMatchInlineSnapshot(` + if (!cond) { + if (cond2) { + console.log("branch 2"); + } + } + `); + + expectJS(` + if (!cond) { + console.log("branch 1"); + } else { + console.log("branch 2"); + }`).toMatchInlineSnapshot(` + if (cond) { + console.log("branch 2"); + } else { + console.log("branch 1"); + } + `); +}); test('ignore when it contains other statements', () => expectJS(` diff --git a/packages/webcrack/src/unminify/transforms/merge-else-if.ts b/packages/webcrack/src/unminify/transforms/merge-else-if.ts index f545dce5..03950b3a 100644 --- a/packages/webcrack/src/unminify/transforms/merge-else-if.ts +++ b/packages/webcrack/src/unminify/transforms/merge-else-if.ts @@ -1,24 +1,73 @@ import * as m from '@codemod/matchers'; -import type { Transform } from '../../ast-utils'; +import * as t from '@babel/types'; +import { applyTransforms, type Transform } from '../../ast-utils'; +import invertBooleanLogic from './invert-boolean-logic'; +import removeDoubleNot from './remove-double-not'; export default { name: 'merge-else-if', tags: ['safe'], visitor() { const nestedIf = m.capture(m.ifStatement()); - const matcher = m.ifStatement( + const matcherElse = m.ifStatement( m.anything(), m.anything(), m.blockStatement([nestedIf]), ); + const matcherIf = m.ifStatement( + m.anything(), + m.blockStatement([nestedIf]), + m.anything(), + ); + const matchIfNegation = m.ifStatement( + m.unaryExpression('!', m.anything()), + m.anything(), + m.anything(), + ); return { IfStatement: { exit(path) { - if (matcher.match(path.node)) { + // if (cond) { ... } else { if (cond2) { ... } } + // -> if (cond) { ... } else if (!cond2) { ... } + if (matcherElse.match(path.node)) { path.node.alternate = nestedIf.current; this.changes++; } + + // if (cond) { if(cond2) { branch1 } else { branch2 } } else { branch3 } + // -> if (!cond) { branch3 } else if (cond2) { branch1 } else { branch2 } + if ( + matcherIf.match(path.node) && + path.node.alternate && + !nestedIf.match(path.node.alternate) + ) { + path.node.test = t.unaryExpression('!', path.node.test); + path.node.consequent = path.node.alternate!; + path.node.alternate = nestedIf.current; + this.changes += applyTransforms( + path.node, + [invertBooleanLogic, removeDoubleNot], + { + log: false, + }, + ).changes; + this.changes++; + } + + // if (!cond) { branch1 } else { branch2 } + // -> if (cond) { branch2 } else { branch1 } + if ( + matchIfNegation.match(path.node) && + path.node.alternate && + !nestedIf.match(path.node.alternate) + ) { + path.node.test = (path.node.test as t.UnaryExpression).argument; + const temp = path.node.consequent; + path.node.consequent = path.node.alternate!; + path.node.alternate = temp; + this.changes++; + } }, }, };