From d75519639465ebe928666b884faf42dddfa27432 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Sun, 22 Mar 2026 14:15:55 +0700 Subject: [PATCH] Add `renameProperties` support for private class fields and methods --- CHANGELOG.md | 1 + .../replacer/IRenamePropertiesReplacer.ts | 6 +++--- .../RenamePropertiesTransformer.ts | 9 +++++++-- .../replacer/RenamePropertiesReplacer.ts | 10 +++++++--- src/node/NodeFactory.ts | 12 +++++++++++ src/node/NodeMetadata.ts | 4 +++- .../JavaScriptObfuscator.spec.ts | 20 ++++++++++++++----- .../fixtures/private-identifier.js | 11 +++++++++- 8 files changed, 58 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29d164864..755e291b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Change Log v5.4.0 --- * Add support for `import attributes`. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1256 +* Add `renameProperties` support for private class fields and methods (`#foo`, `#bar()`). Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1220 * Fixed `reservedNames` not preserving class method and property names when `stringArray` or `deadCodeInjection` is enabled. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1279 * Replaced `mkdirp` dependency with native `fs.mkdirSync({ recursive: true })`. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1275. Thank you https://github.com/roli-lpci! * Replaced `conf` dependency with custom implementation using `env-paths` and native `fs` diff --git a/src/interfaces/node-transformers/rename-properties-transformers/replacer/IRenamePropertiesReplacer.ts b/src/interfaces/node-transformers/rename-properties-transformers/replacer/IRenamePropertiesReplacer.ts index 18abe84cf..b86590c2c 100644 --- a/src/interfaces/node-transformers/rename-properties-transformers/replacer/IRenamePropertiesReplacer.ts +++ b/src/interfaces/node-transformers/rename-properties-transformers/replacer/IRenamePropertiesReplacer.ts @@ -7,8 +7,8 @@ export interface IRenamePropertiesReplacer { excludePropertyName(propertyName: string): void; /** - * @param {ESTree.Identifier | ESTree.Literal} node - * @returns {ESTree.Identifier | ESTree.Literal} + * @param {ESTree.Identifier | ESTree.Literal | ESTree.PrivateIdentifier} node + * @returns {ESTree.Identifier | ESTree.Literal | ESTree.PrivateIdentifier} */ - replace(node: ESTree.Identifier | ESTree.Literal): ESTree.Identifier | ESTree.Literal; + replace(node: ESTree.Identifier | ESTree.Literal | ESTree.PrivateIdentifier): ESTree.Identifier | ESTree.Literal | ESTree.PrivateIdentifier; } diff --git a/src/node-transformers/rename-properties-transformers/RenamePropertiesTransformer.ts b/src/node-transformers/rename-properties-transformers/RenamePropertiesTransformer.ts index 685b6d0d4..4a0b45b68 100644 --- a/src/node-transformers/rename-properties-transformers/RenamePropertiesTransformer.ts +++ b/src/node-transformers/rename-properties-transformers/RenamePropertiesTransformer.ts @@ -48,7 +48,7 @@ export class RenamePropertiesTransformer extends AbstractNodeTransformer { >( propertyNode: TNode, propertyKeyNode: ESTree.Expression | ESTree.PrivateIdentifier - ): propertyKeyNode is ESTree.Identifier | ESTree.Literal { + ): propertyKeyNode is ESTree.Identifier | ESTree.Literal | ESTree.PrivateIdentifier { if (NodeGuards.isIdentifierNode(propertyKeyNode) && propertyNode.computed) { return false; } @@ -111,8 +111,13 @@ export class RenamePropertiesTransformer extends AbstractNodeTransformer { * @param {NodeGuards} parentNode * @returns {Node} */ + // eslint-disable-next-line complexity public transformNode(node: ESTree.Node, parentNode: ESTree.Node): ESTree.Node { - if (!NodeGuards.isIdentifierNode(node) && !NodeGuards.isLiteralNode(node)) { + if ( + !NodeGuards.isIdentifierNode(node) && + !NodeGuards.isLiteralNode(node) && + !NodeGuards.isPrivateIdentifierNode(node) + ) { return node; } diff --git a/src/node-transformers/rename-properties-transformers/replacer/RenamePropertiesReplacer.ts b/src/node-transformers/rename-properties-transformers/replacer/RenamePropertiesReplacer.ts index 707a49251..f657b0aaa 100644 --- a/src/node-transformers/rename-properties-transformers/replacer/RenamePropertiesReplacer.ts +++ b/src/node-transformers/rename-properties-transformers/replacer/RenamePropertiesReplacer.ts @@ -80,10 +80,14 @@ export class RenamePropertiesReplacer implements IRenamePropertiesReplacer { } /** - * @param {ESTree.Identifier | ESTree.Literal} node - * @returns {ESTree.Identifier | ESTree.Literal} + * @param {ESTree.Identifier | ESTree.Literal | ESTree.PrivateIdentifier} node + * @returns {ESTree.Identifier | ESTree.Literal | ESTree.PrivateIdentifier} */ - public replace(node: ESTree.Identifier | ESTree.Literal): ESTree.Identifier | ESTree.Literal { + public replace(node: ESTree.Identifier | ESTree.Literal | ESTree.PrivateIdentifier): ESTree.Identifier | ESTree.Literal | ESTree.PrivateIdentifier { + if (NodeGuards.isPrivateIdentifierNode(node)) { + return NodeFactory.privateIdentifierNode(this.replacePropertyName(node.name)); + } + if (NodeGuards.isIdentifierNode(node)) { return NodeFactory.identifierNode(this.replacePropertyName(node.name)); } diff --git a/src/node/NodeFactory.ts b/src/node/NodeFactory.ts index 6057ea1ca..4bb3a813b 100644 --- a/src/node/NodeFactory.ts +++ b/src/node/NodeFactory.ts @@ -373,6 +373,18 @@ export class NodeFactory { }; } + /** + * @param {string} name + * @returns {PrivateIdentifier} + */ + public static privateIdentifierNode(name: string): ESTree.PrivateIdentifier { + return { + type: NodeType.PrivateIdentifier, + name, + metadata: { ignoredNode: false } + }; + } + /** * @param {(ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier)[]} specifiers * @param {Literal} source diff --git a/src/node/NodeMetadata.ts b/src/node/NodeMetadata.ts index 30fc98d8e..adb735d04 100644 --- a/src/node/NodeMetadata.ts +++ b/src/node/NodeMetadata.ts @@ -49,7 +49,9 @@ export class NodeMetadata { * @param {Identifier | Literal} node * @returns {boolean} */ - public static isPropertyKeyToRenameNode(node: ESTree.Identifier | ESTree.Literal): boolean { + public static isPropertyKeyToRenameNode( + node: ESTree.Identifier | ESTree.PrivateIdentifier | ESTree.Literal + ): boolean { return ( NodeMetadata.get( node, diff --git a/test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts b/test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts index d347675f9..a268e201c 100644 --- a/test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts +++ b/test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts @@ -910,9 +910,7 @@ describe('JavaScriptObfuscator', () => { }); describe('Private identifiers support', () => { - const regExp: RegExp = new RegExp( - 'class Foo *{ *' + '#bar *= *0x1; *' + "\\['method'] *\\(\\) *{ *" + 'this\.#bar *= *0x2;' + '} *' + '}' - ); + const variableMatch: string = '_0x([a-f0-9]){4,6}'; let obfuscatedCode: string; @@ -925,8 +923,20 @@ describe('JavaScriptObfuscator', () => { }).getObfuscatedCode(); }); - it('should support private identifiers', () => { - assert.match(obfuscatedCode, regExp); + it('should rename private field', () => { + assert.match(obfuscatedCode, new RegExp(`#${variableMatch} *= *0x1`)); + }); + + it('should rename private field access', () => { + assert.match(obfuscatedCode, new RegExp(`this\\.#${variableMatch} *= *0x2`)); + }); + + it('should rename private method declaration', () => { + assert.match(obfuscatedCode, new RegExp(`#${variableMatch}\\(\\) *\\{`)); + }); + + it('should rename private method call', () => { + assert.match(obfuscatedCode, new RegExp(`this\\.#${variableMatch}\\(\\)`)); }); }); diff --git a/test/functional-tests/javascript-obfuscator/fixtures/private-identifier.js b/test/functional-tests/javascript-obfuscator/fixtures/private-identifier.js index fff5f2562..96faea9bc 100644 --- a/test/functional-tests/javascript-obfuscator/fixtures/private-identifier.js +++ b/test/functional-tests/javascript-obfuscator/fixtures/private-identifier.js @@ -4,4 +4,13 @@ class Foo { method() { this.#bar = 2; } -} \ No newline at end of file + + #privateMethod() { + return this.#bar; + } + + run() { + this.method(); + return this.#privateMethod(); + } +}