Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
e39a552
previous
echo094 Sep 2, 2024
2befacd
fix(decode-js): use direct ESM default imports for Babel 8 in jsconfuser
echo094 Jul 21, 2026
3997497
feat(decode-js): decode js-confuser Flatten transform
echo094 Jul 21, 2026
6018645
fix(decode-js): return unmodified ast when jsconfuser input isn't Pac…
echo094 Jul 22, 2026
9ec2710
fix(decode-js): guard undefined binding in deStringConcealingPlace
echo094 Jul 22, 2026
b20c1f0
feat(decode-js): decode js-confuser Integrity and partial Lock transf…
echo094 Jul 22, 2026
522e8de
fix(decode-js): rewrite string-concealing and global-concealing for c…
echo094 Jul 22, 2026
f0f08fe
feat(decode-js): decode dateLock, domainLock, and tamperProtection
echo094 Jul 22, 2026
3c28c24
fix(decode-js): handle Preparation-normalized keys in checkFuncLen
echo094 Jul 22, 2026
db2d0c7
refactor(decode-js): fix stale Lock comment in jsconfuser.js
echo094 Jul 22, 2026
5c41569
feat(decode-js): decode RGF eval-wrapped sub-programs
echo094 Jul 22, 2026
1c45d84
feat(decode-js): decode Dispatcher per-block function-table calls
echo094 Jul 22, 2026
87fc676
feat(decode-js): decode DeadCode and rewrite OpaquePredicates for cur…
echo094 Jul 22, 2026
564b53d
feat(decode-js): decode Calculator dispatch calls
echo094 Jul 22, 2026
880d48d
refactor(decode-js): split stack.js into variable-masking.js and func…
echo094 Jul 22, 2026
e1ace74
fix(decode-js): finish VariableMasking coverage - alias safety, lengt…
echo094 Jul 22, 2026
024ebf7
feat(decode-js): rewrite DuplicateLiteralsRemoval decoder for current…
echo094 Jul 22, 2026
f25d460
feat(decode-js): undo ControlFlowFlattening literal entanglement
echo094 Jul 22, 2026
4184246
feat(decode-js): resolve ControlFlowFlattening's transition-graph pri…
echo094 Jul 22, 2026
7e7a1e3
feat(decode-js): walk ControlFlowFlattening's transition graph into a…
echo094 Jul 23, 2026
c479a2d
feat(decode-js): undo literal entanglement over the resolved block graph
echo094 Jul 23, 2026
75877a8
feat(decode-js): flatten scope-object member chains back to identifiers
echo094 Jul 23, 2026
ff4f116
feat(decode-js): fold branch nodes back into real if/else AST
echo094 Jul 23, 2026
36cdfe6
feat(decode-js): declare identifiers introduced by scope flattening
echo094 Jul 23, 2026
119e9e1
feat(decode-js): decode ControlFlowFlattening's outlined nested funct…
echo094 Jul 23, 2026
0f613d2
feat(decode-js): wire ControlFlowFlattening's graph decoder into the …
echo094 Jul 23, 2026
6e82256
fix(decode-js): remove dead deMinifyArrow, superseded by function-len…
echo094 Jul 23, 2026
fa25a67
feat(decode-js): wire delete-extra.js into jsconfuser for Finalizer
echo094 Jul 23, 2026
c151869
feat(decode-js): decode AstScrambler statement-merging
echo094 Jul 23, 2026
26c641e
fix(decode-js): guard FunctionLength against non-rest-masked targets
echo094 Jul 23, 2026
77e4dab
fix(decode-js): fix AntiTooling's broken guard conditions
echo094 Jul 23, 2026
32beb8a
test(decode-js): add lock.domainLock + stringConcealing integration f…
echo094 Jul 23, 2026
55844da
feat(decode-js): reconstruct local vars for masked dynamic values
echo094 Jul 23, 2026
9a73e8a
test(jsconfuser): add rgf/flatten + preserveFunctionLength fixtures
echo094 Jul 23, 2026
d4c5dfe
fix(decode-js): resolve binary-expression locals in VariableMasking
echo094 Jul 23, 2026
758142d
fix(decode-js): unmask Dispatcher fns entries before matching
echo094 Jul 23, 2026
2df747a
fix(decode-js): accept AntiTooling-stripped fnLength shape in Dispatcher
echo094 Jul 23, 2026
443a401
test(decode-js): add inline-flattened CFF interpreter fixtures (gap #…
echo094 Jul 23, 2026
438a87a
feat(decode-js): detect inline-flattened CFF functions (gap #2 M1)
echo094 Jul 23, 2026
e65ae52
feat(decode-js): detect scope-member-state inline interpreters (gap #…
echo094 Jul 23, 2026
278b703
feat(decode-js): accept the (1,fn) comma-guard wrapper callee
echo094 Jul 23, 2026
0914b84
feat(decode-js): decode inline-flattened CFF functions (gap #2 N2)
echo094 Jul 23, 2026
801a19e
fix(decode-js): guard null id in VariableMasking debug log
echo094 Jul 24, 2026
6453ebb
fix(decode-js): reverse Pack trailing-return to keep decoded output a…
echo094 Jul 24, 2026
45c3bf1
fix(decode-js): decode CFF applications under minify (braceless while…
echo094 Jul 24, 2026
83d0f0e
test(decode-js): retrofit fixture triples with reserved .src.js sources
echo094 Jul 24, 2026
38dcd9b
feat(decode-js): collapse narrow CFF+dispatcher closure residue
echo094 Jul 24, 2026
9a4609a
fix(decode-js): prevent outer-reference capture in Flatten under rena…
echo094 Jul 25, 2026
f421503
test(decode-js): confirm Lock decode is safe under renameVariables
echo094 Jul 25, 2026
670fa35
test(decode-js): confirm RGF decode is safe under renameVariables
echo094 Jul 25, 2026
fb5c83d
test(decode-js): confirm Dispatcher decode is safe under renameVariables
echo094 Jul 25, 2026
fd5d3c0
test(decode-js): verify dead-code decoder safe under renameVariables
echo094 Jul 25, 2026
c61f627
fix(decode-js): resolve calculator dispatch fn binding from parent scope
echo094 Jul 25, 2026
56072de
fix(decode-js): harden global-concealing switch-fn binding lookup
echo094 Jul 25, 2026
90a4a0d
test(decode-js): verify opaque-predicates decoder safe under renameVa…
echo094 Jul 25, 2026
f767516
test(decode-js): note StringSplitting verified safe under renameVaria…
echo094 Jul 25, 2026
44d78a9
fix(decode-js): key StringConcealing's eval bundle by node, not name
echo094 Jul 25, 2026
39d6fa2
fix(decode-js): scope-check VariableMasking's stack-param matching un…
echo094 Jul 25, 2026
6beccc2
test(decode-js): verify duplicate-literal safe under renameVariables
echo094 Jul 25, 2026
a063bc6
test(decode-js): audit MovedDeclarations for renameVariables name-rel…
echo094 Jul 25, 2026
54ac0e1
test(decode-js): audit AstScrambler for renameVariables name-reliance
echo094 Jul 25, 2026
cee03c0
fix(decode-js): guard checkControlVar against an unbound control vari…
echo094 Jul 25, 2026
bf59f74
fix(decode-js): identify CFF runtime helpers structurally, not by name
echo094 Jul 25, 2026
b2dfa9d
test(decode-js): pin CFF's renameVariables decode with a fixture triple
echo094 Jul 25, 2026
ff74ec1
fix(decode-js): accept a minified bare `return;` as a CFF block termi…
echo094 Jul 25, 2026
c209a0b
test(decode-js): pin the minified bare-`return;` decode with a fixtur…
echo094 Jul 25, 2026
31db9c6
fix(decode-js): accept MovedDeclarations' hoisted CFF entry harness
echo094 Jul 25, 2026
6e4fafb
feat(decode-js): reverse MovedDeclarations' function-parameter packing
echo094 Jul 25, 2026
6aa7b1b
test(decode-js): pin MovedDeclarations' parameter-packing reversal
echo094 Jul 25, 2026
0ce2a1d
fix(decode-js): accept AstScrambler's dissolved CFF goto partition
echo094 Jul 26, 2026
bf6fea0
test(decode-js): pin the AstScrambler goto-partition decode
echo094 Jul 26, 2026
1820223
fix(decode-js): accept `throw` as a CFF block terminal
echo094 Jul 26, 2026
73abe31
test(decode-js): pin the flattened dead-code `throw` decode
echo094 Jul 26, 2026
6df26ff
fix(decode-js): decode the literal array under minify and CFF
echo094 Jul 26, 2026
f9d7c96
feat(decode-js): collapse the inline-flattened CFF call harness
echo094 Jul 26, 2026
f38c960
test(decode-js): pin the inline-flattened harness collapse
echo094 Jul 26, 2026
450822b
fix(visitor/duplicate-literal): accept the moved-declaration array sp…
echo094 Jul 26, 2026
7271cdc
fix(visitor/duplicate-literal): skip an array that is reassigned
echo094 Jul 26, 2026
1e14a83
fix(plugin/jsconfuser): decode VariableMasking again after the litera…
echo094 Jul 26, 2026
f81c434
feat(visitor/variable-masking): restore the parameter list and slot v…
echo094 Jul 26, 2026
866365d
fix(visitor/string-concealing): guard deStringConcealingPlace's inlining
echo094 Jul 27, 2026
6370f3f
fix(visitor/string-concealing): clone the inlined literal per reference
echo094 Jul 27, 2026
e48fa83
fix(visitor/string-concealing): resolve the array from its binding
echo094 Jul 27, 2026
46ad563
fix(plugin/jsconfuser): decode the string layer again after the CFF d…
echo094 Jul 27, 2026
6aee6bf
fix(visitor/variable-masking): un-mask through a stack alias
echo094 Jul 27, 2026
f3bfae8
fix(visitor/variable-masking): find the truncation write through the …
echo094 Jul 27, 2026
449ad1c
fix(visitor/string-concealing): build the eval bundle without source …
echo094 Jul 28, 2026
6060fb3
feat(visitor/string-concealing): decode wrappers that are not declara…
echo094 Jul 28, 2026
7a3b2dd
fix(visitor/string-concealing): pick forward references by document o…
echo094 Jul 28, 2026
a73b25f
feat(visitor/variable-masking): un-mask a fully-folded rest param
echo094 Jul 28, 2026
db62acb
fix(visitor/moved-declarations): remove dead packed-parameter slots
echo094 Jul 28, 2026
fa2aec5
feat(decode-js): resolve a binding to its real definition site
echo094 Jul 28, 2026
82149b3
fix(visitor/dead-code): match the guard's real spellings
echo094 Jul 28, 2026
6c21f0f
fix(plugin/jsconfuser): re-run DeadCode and OpaquePredicates after CFF
echo094 Jul 28, 2026
8c6b71f
fix(visitor/string-concealing): correct the (1, f)() provenance comment
echo094 Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fs from 'fs'
import { parseArgs } from 'node:util'
import PluginCommon from './plugin/common.js'
import PluginJjencode from './plugin/jjencode.js'
import PluginJsconfuser from './plugin/jsconfuser.js'
import PluginSojson from './plugin/sojson.js'
import PluginSojsonV7 from './plugin/sojsonv7.js'
import PluginObfuscator from './plugin/obfuscator.js'
Expand All @@ -25,6 +26,7 @@ console.log(`Output: ${decodeFile}`)
const plugins = {
common: PluginCommon,
jjencode: PluginJjencode,
jsconfuser: PluginJsconfuser,
sojson: PluginSojson,
sojsonv7: PluginSojsonV7,
obfuscator: PluginObfuscator,
Expand Down
187 changes: 187 additions & 0 deletions src/plugin/jsconfuser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import { parse } from '@babel/parser'
import generator from '@babel/generator'
import traverse from '@babel/traverse'

import calculateConstantExp from '../visitor/calculate-constant-exp.js'
import deleteExtra from '../visitor/delete-extra.js'
import pruneIfBranch from '../visitor/prune-if-branch.js'
import splitVarDeclaration from '../visitor/split-variable-declaration.js'
import jcAntiTooling from '../visitor/jsconfuser/anti-tooling.js'
import jcAstScramblerInit from '../visitor/jsconfuser/ast-scrambler.js'
import jcCalculatorInit from '../visitor/jsconfuser/calculator.js'
import jcControlFlow from '../visitor/jsconfuser/control-flow.js'
import jcControlFlowGraph from '../visitor/jsconfuser/control-flow-graph.js'
import jcDeadCodeInit from '../visitor/jsconfuser/dead-code.js'
import jcDispatcherInit from '../visitor/jsconfuser/dispatcher.js'
import jcDuplicateLiteralInit from '../visitor/jsconfuser/duplicate-literal.js'
import jcFlatten from '../visitor/jsconfuser/flatten.js'
import jcFuncLengthInit from '../visitor/jsconfuser/function-length.js'
import jcGlobalConcealingInit from '../visitor/jsconfuser/global-concealing.js'
import jcIntegrityInit from '../visitor/jsconfuser/integrity.js'
import jcLockInit from '../visitor/jsconfuser/lock.js'
import jcMovedDeclarationsInit from '../visitor/jsconfuser/moved-declarations.js'
import jcOpaquePredicates from '../visitor/jsconfuser/opaque-predicates.js'
import jcPack from '../visitor/jsconfuser/pack.js'
import jcRgf from '../visitor/jsconfuser/rgf.js'
import jcStringCompression from '../visitor/jsconfuser/string-compression.js'
import jcStringConceal from '../visitor/jsconfuser/string-concealing.js'
import jcVariableMasking from '../visitor/jsconfuser/variable-masking.js'

export default function (code) {
let ast
try {
ast = parse(code, { errorRecovery: true })
} catch (e) {
console.error(`Cannot parse code: ${e.reasonCode}`)
return null
}
// Pack
ast = jcPack(ast)
// Integrity (must decode before anything reshapes the hashed functions further -
// Integrity is the encoder's last stage, so this is its least-processed form)
traverse(ast, jcIntegrityInit())
// AntiTooling
traverse(ast, jcAntiTooling)
// AstScrambler (must run before every other structural decoder below -
// AstScrambler is one of the last encoder stages (order 29) and merges
// whatever ExpressionStatements are still standalone at that point into a
// single no-op call's argument list, including ones other transforms'
// decoders pattern-match as standalone statements, e.g. ControlFlowFlattening's
// state assignments and Lock's inserted checks - undo it first so those
// patterns are whole again)
traverse(ast, jcAstScramblerInit())
// Minify (no dedicated decode step: literal shortenings (!0/!1, void 0, 1/0)
// and merged `var` declarations are already undone below by the generic
// calculateConstantExp/splitVarDeclaration passes; everything else Minify
// does is either cosmetic reformatting that reads fine as-is or dead-code
// elimination with nothing left to recover - see minify.md)
// MovedDeclarations (two separate rewrites: the generic splitVarDeclaration pass
// undoes the merged `var` declarations its block-hoisting produces, and
// deMovedDeclarations undoes its function-parameter packing. Both must run before
// any pass that identifies structure by looking for a FunctionDeclaration or a
// declaration-shaped statement - most importantly the ControlFlowFlattening decode
// below, whose whole application fails closed on a packed `_main`.)
traverse(ast, splitVarDeclaration)
traverse(ast, jcMovedDeclarationsInit())
// DuplicateLiteralsRemoval (must run before anything that matches literal
// shapes directly - Calculator's operator keys, StringConcealing's slice
// args, GlobalConcealing's switch keys, etc. - since DuplicateLiteralsRemoval
// runs after all of them on the encode side and can fold any of their own
// repeated literals into this array too)
traverse(ast, jcDuplicateLiteralInit())
// VariableMasking + the shared FunctionLength helper (preserveFunctionLength
// can be emitted by Dispatcher/RGF/Flatten/VariableMasking alike)
traverse(ast, jcFuncLengthInit())
traverse(ast, jcVariableMasking)
// StringCompression
traverse(ast, jcStringCompression)
// StringConcealing
traverse(ast, jcStringConceal.deStringConcealingInit())
traverse(ast, jcStringConceal.deStringConcealingPlace)
// Calculator (unwraps the {ph}_calc(operator, a, b) dispatch call back to a
// plain BinaryExpression; the fold to a literal happens in the generic
// calculate-constant-exp.js pass right below)
traverse(ast, jcCalculatorInit())
// StringSplitting
traverse(ast, calculateConstantExp)
// VariableMasking (run again)
traverse(ast, jcVariableMasking)
// OpaquePredicates
traverse(ast, jcOpaquePredicates.deOpaquePredicates)
traverse(ast, jcOpaquePredicates.dePredicateGenInit())
traverse(ast, calculateConstantExp)
traverse(ast, pruneIfBranch)
// DeadCode (matches and removes the whole guard as one self-contained unit -
// must run after OpaquePredicates above, since OpaquePredicates' own IfStatement
// wrapping fires generically on every if, including ones DeadCode already
// inserted - `if(!(x1 in dummy1) && (x2 in dummy2))`, needing OpaquePredicates'
// own fold to unwrap first before DeadCode's guard is bare again)
traverse(ast, jcDeadCodeInit())
// GlobalConcealing
traverse(ast, jcGlobalConcealingInit())
// ControlFlowFlattening (the main state-vector/transition-graph mechanism - run before
// deControlFlowFlatteningStateless below, which targets a separate, minor sub-case
// unrelated to this one and can still run on whatever this pass doesn't match)
traverse(ast, jcControlFlowGraph.deControlFlowFlatteningGraphInit())
traverse(ast, jcControlFlow.deControlFlowFlatteningStateless)
traverse(ast, calculateConstantExp)
// DuplicateLiteralsRemoval (again). ControlFlowFlattening is encoder Order 24 and
// DuplicateLiteralsRemoval Order 22, so CFF runs *after* it and rewrites part of its
// reference sites into indexes through the CFF state array -
// `literals[state[0x45] + 0x377]`. Those are unreadable to the early pass above and only
// become plain numeric literals once the CFF decode and the folding above have run, so
// the array needs a second visit here. The early pass still has to stay where it is:
// Calculator, StringConcealing and GlobalConcealing all match literal shapes and run
// before this point.
traverse(ast, jcDuplicateLiteralInit())
// VariableMasking (a third time). VariableMasking is encoder Order 20 and
// DuplicateLiteralsRemoval Order 22, so the array pass above can be holding this
// transform's own mask keys - a slot spelled `stk[literals[4]]` rather than
// `stk["length"]`. Both passes above run before the array resolves, and every such key
// reads as unmatchable there, so the whole enclosing function is left masked. This pass
// sees plain keys and is what actually decodes those functions.
traverse(ast, jcVariableMasking)
// StringCompression + StringConcealing (again). Same reason DuplicateLiteralsRemoval and
// VariableMasking above need a second visit, applied to the string layer: on a `high`
// sample the whole program is still inside the ControlFlowFlattening interpreter when the
// early string passes run, so no `{ph}_STR_N(start, length)` wrapper exists for them to
// match yet and they decode nothing at all. The wrappers only become distinct functions
// once the CFF decode above has run. The early passes still have to stay where they are -
// deStringConcealingPlace does fire there, on shapes that are already plain assignments.
//
// This has to sit *before* Lock/RGF/Dispatcher/Flatten below rather than at the end of the
// pipeline: Dispatcher in particular reads its four secret flag/key strings out of the
// matched dispatcher body and needs them as StringLiterals, which is exactly what these
// passes produce.
traverse(ast, jcStringCompression)
traverse(ast, jcStringConceal.deStringConcealingInit())
traverse(ast, jcStringConceal.deStringConcealingPlace)
traverse(ast, calculateConstantExp)
// OpaquePredicates + DeadCode (again). Same reason as the passes above, and the same
// remedy: on a `high` sample both transforms' guards sit inside the ControlFlowFlattening
// interpreter when the early passes run, so there is no `if ("randomProp" in dummyFn)`
// statement in the tree for them to visit yet and they remove nothing. The guards only
// become ordinary statements once the CFF decode above has unwound the interpreter. The
// early passes still have to stay where they are - they do fire on whatever is already at
// Program level, and DeadCode's own comment above explains why it must follow
// OpaquePredicates' fold either way.
//
// This sits before Dispatcher below for the ordering reason that motivated it: DeadCode is
// encoder Order 8 and OpaquePredicates 13, both *later* than Dispatcher's Order 6, so
// until their guards are gone the dispatcher matcher is reading a body they have padded -
// every such guard is an extra statement in it, which is exactly what its body-shape check
// rejects on.
traverse(ast, jcOpaquePredicates.dePredicateGenInit())
traverse(ast, calculateConstantExp)
traverse(ast, pruneIfBranch)
traverse(ast, jcDeadCodeInit())
// Lock (all six sub-features: antiDebug, selfDefending, dateLock, domainLock,
// tamperProtection, and invokeCountermeasures cleanup - run this late, mirroring
// Flatten below, since Lock runs early on the encode side and its inserted code
// is reshaped by nearly every later transform)
traverse(ast, jcLockInit())
// RGF (recursively decodes each eval-wrapped sub-program with this same
// pipeline - must run after the index-literal folding above so the array
// index reads as a plain NumericLiteral)
traverse(ast, jcRgf)
// Dispatcher (needs VariableMasking, above, to have already unmasked the
// dispatcher function's own params if VariableMasking also applies to it)
traverse(ast, jcDispatcherInit())
// Dispatcher-closure residue from CFF+Dispatcher stacking (checkpoint gap #2 N4.2) -
// must run after the Dispatcher pass above normalizes the dispatch object/keys, so a
// call site's key matches the impl object property it selects.
traverse(ast, jcControlFlowGraph.deDispatcherClosureInit())
// Flatten
traverse(ast, jcFlatten)
// Finalizer (hexadecimalNumbers/stringEncoding: both just re-escape a
// literal's raw source text - the parsed VALUE is already identical, but
// Babel's generator prefers node.extra.raw over the value when present, so
// it would otherwise print the escaped/hex form back out unchanged)
traverse(ast, deleteExtra)
// ExpressionObfuscation
code = generator(ast, {
comments: false,
jsescOption: { minimal: true },
}).code
return code
}
80 changes: 80 additions & 0 deletions src/utility/binding-def.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Resolving a binding to what it actually *defines*, rather than to one spelling of its
* declaration.
*
* `binding.path` is only the definition site for the plainest spelling. Two encoder
* mechanisms routinely move it elsewhere while leaving the binding intact, and a matcher
* written as "a binding whose path is a FunctionDeclaration" - the rename-proof idiom this
* decoder uses throughout - fails closed on both:
*
* - a declaration split into `var X;` + `X = <value>;`, where `binding.path` is a
* declarator with a null `init` and the value is in the binding's single write;
* - a name hoisted onto the enclosing function's parameter list, where `binding.kind`
* reads 'param', `binding.path` is the parameter's own `Identifier`, and the real
* definition is demoted to a `constantViolations` entry.
*
* Fail-closed throughout: a binding written more than once has no single definition, so
* these return null rather than pick one. "Left alone" is always safe; a guess is not.
*/
import * as t from '@babel/types'

/**
* The single value a binding holds, as both node and path, or null.
*/
function resolveBindingValue(binding) {
if (!binding || !binding.path) {
return null
}
const declPath = binding.path
if (declPath.isVariableDeclarator() && declPath.node.init) {
// A declarator with an initializer defines its value outright; a further write would
// mean the name does not hold one single thing.
if (binding.constantViolations.length !== 0) {
return null
}
return { node: declPath.node.init, path: declPath.get('init') }
}
if (binding.constantViolations.length !== 1) {
return null
}
const write = binding.constantViolations[0]
if (!write.isAssignmentExpression() || write.node.operator !== '=') {
return null
}
const right = write.get('right')
return right.node ? { node: right.node, path: right } : null
}

/**
* The function a binding names, whatever spelling it arrived in: a `FunctionDeclaration`
* (whether at `binding.path` or demoted to a constant violation by parameter hoisting), or a
* function-valued declarator/assignment. Returns the `Function`'s own NodePath, or null.
*/
function resolveBindingFunction(binding) {
if (!binding || !binding.path) {
return null
}
if (binding.path.isFunctionDeclaration()) {
return binding.path
}
// A hoisted parameter's declaration survives as a write, and for a packed function
// declaration that write is the FunctionDeclaration itself rather than an assignment.
if (binding.kind === 'param') {
const decls = binding.constantViolations.filter((v) =>
v.isFunctionDeclaration(),
)
if (decls.length === 1) {
return decls[0]
}
}
const value = resolveBindingValue(binding)
if (!value || !t.isFunction(value.node)) {
return null
}
return value.path
}

export default {
resolveBindingValue,
resolveBindingFunction,
}
15 changes: 15 additions & 0 deletions src/utility/check-func.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
function checkPattern(code, pattern) {
let i = 0
let j = 0
while (i < code.length && j < pattern.length) {
if (code[i] == pattern[j]) {
++j
}
++i
}
return j == pattern.length
}

export default {
checkPattern,
}
75 changes: 75 additions & 0 deletions src/utility/safe-func.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as t from '@babel/types'

function safeDeleteNode(name, path) {
let binding
if (path.isFunctionDeclaration()) {
binding = path.parentPath.scope.getBinding(name)
} else {
binding = path.scope.getBinding(name)
}
if (!binding) {
return false
}
binding.scope.crawl()
binding = binding.scope.getBinding(name)
if (binding.references) {
return false
}
for (const item of binding.constantViolations) {
item.remove()
}
const decl = binding.path
if (decl.removed) {
return true
}
if (!decl.isVariableDeclarator() && !decl.isFunctionDeclaration()) {
return true
}
binding.path.remove()
return true
}

function safeGetLiteral(path) {
if (path.isUnaryExpression()) {
if (path.node.operator === '-' && path.get('argument').isNumericLiteral()) {
return -1 * path.get('argument').node.value
}
return null
}
if (path.isLiteral()) {
return path.node.value
}
return null
}

function safeGetName(path) {
if (path.isIdentifier()) {
return path.node.name
}
if (path.isLiteral()) {
return path.node.value
}
if (path.isUnaryExpression()) {
return safeGetLiteral(path)
}
return null
}

function safeReplace(path, value) {
if (typeof value === 'string') {
path.replaceWith(t.stringLiteral(value))
return
}
if (typeof value === 'number') {
path.replaceWith(t.numericLiteral(value))
return
}
path.replaceWithSourceString(value)
}

export default {
safeDeleteNode,
safeGetLiteral,
safeGetName,
safeReplace,
}
Loading
Loading