Add renameProperties support for private class fields and methods (#1393)

This commit is contained in:
Timofey Kachalov
2026-03-22 11:33:36 +04:00
committed by GitHub
parent e1d9435468
commit d8db02336d
8 changed files with 58 additions and 15 deletions
+1
View File
@@ -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
* Fixed infinite loop / stack overflow when `reservedNames` patterns match all generated identifier names. Now throws a descriptive error instead. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1382
* Fixed `transformObjectKeys` changing evaluation order when object expression is inside a sequence expression with preceding side effects (e.g. `return aux(ys), { min }`). Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1246
@@ -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;
}
@@ -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;
}
@@ -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));
}
+12
View File
@@ -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
+3 -1
View File
@@ -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<ESTree.IdentifierNodeMetadata | ESTree.LiteralNodeMetadata, 'propertyKeyToRenameNode'>(
node,
@@ -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}\\(\\)`));
});
});
@@ -4,4 +4,13 @@ class Foo {
method() {
this.#bar = 2;
}
}
#privateMethod() {
return this.#bar;
}
run() {
this.method();
return this.#privateMethod();
}
}