UNPKG

wgsl-plus

Version:

A WGSL preprocessor, prettifier, minifier, obfuscator, and compiler with C-style macros, conditional compilation, file linking, and multi-format output for WebGPU shaders.

618 lines (538 loc) 24.6 kB
import collectDeclaredIdentifiersAndEntryPoints from '../src/tools/obfuscation/collect-declared-identifiers-and-entry-points'; import collectStructNames from '../src/tools/obfuscation/collect-struct-names'; import { resetNameIndex } from '../src/tools/obfuscation/next-name'; import obfuscate from '../src/tools/obfuscation/obfuscate'; // Adjust path as needed import replaceIdentifiers from '../src/tools/obfuscation/replace-identifiers'; import Token from '../src/tokenization/token'; import tokenizeWgsl from '../src/tokenization/tokenize-wgsl'; describe("collectStructNames", () => { beforeEach(() => { // Reset nameIndex before each test for consistent obfuscated names resetNameIndex(); }); it("maps non-swizzle struct members to obfuscated names", () => { const code = "struct MyStruct { field1: f32, field2: i32 };"; const tokens = tokenizeWgsl(code); const structMemberMap = collectStructNames(tokens); expect(structMemberMap.size).toBe(2); expect(structMemberMap.get("field1")).toBe("_0"); expect(structMemberMap.get("field2")).toBe("_1"); }); it("does not map swizzle-like struct members", () => { const code = "struct VectorLike { x: f32, y: f32, z: f32, w: f32, xy: vec2<f32> };"; const tokens = tokenizeWgsl(code); const structMemberMap = collectStructNames(tokens); expect(structMemberMap.size).toBe(0); // All are potential swizzles expect(structMemberMap.get("x")).toBeUndefined(); expect(structMemberMap.get("y")).toBeUndefined(); expect(structMemberMap.get("z")).toBeUndefined(); expect(structMemberMap.get("w")).toBeUndefined(); expect(structMemberMap.get("xy")).toBeUndefined(); }); it("maps overlapping members across structs to the same name if non-swizzle", () => { const code = "struct A { common: i32, uniqueA: f32 }; struct B { common: i32, uniqueB: vec2<f32> };"; const tokens = tokenizeWgsl(code); const structMemberMap = collectStructNames(tokens); expect(structMemberMap.size).toBe(3); expect(structMemberMap.get("common")).toBe("_0"); expect(structMemberMap.get("uniqueA")).toBe("_1"); expect(structMemberMap.get("uniqueB")).toBe("_2"); }); it("handles empty structs with no mappings", () => { const code = "struct EmptyStruct {};"; const tokens = tokenizeWgsl(code); const structMemberMap = collectStructNames(tokens); expect(structMemberMap.size).toBe(0); }); it("processes incomplete structs gracefully", () => { const code = "struct Incomplete { field: f32"; // No closing brace const tokens = tokenizeWgsl(code); const structMemberMap = collectStructNames(tokens); expect(structMemberMap.size).toBe(1); expect(structMemberMap.get("field")).toBe("_0"); }); it("ignores swizzle-like members even with mixed cases", () => { const code = "struct Mixed { pos: vec3<f32>, x: f32, xyz: f32 };"; const tokens = tokenizeWgsl(code); const structMemberMap = collectStructNames(tokens); expect(structMemberMap.size).toBe(1); expect(structMemberMap.get("pos")).toBe("_0"); expect(structMemberMap.get("x")).toBeUndefined(); expect(structMemberMap.get("xyz")).toBeUndefined(); }); }); describe("collectDeclaredIdentifiersAndEntryPoints", () => { beforeEach(() => { resetNameIndex(); // Reset name index for predictable obfuscated names (_0, _1, etc.) }); it("maps top-level variables", () => { const code = "let a: i32 = 5; var b: f32; const c = 10;"; const tokens = tokenizeWgsl(code); const structMemberMap = collectStructNames(tokens); const { identifierMap } = collectDeclaredIdentifiersAndEntryPoints(tokens); expect(identifierMap.size).toBe(3); expect(identifierMap.get("a")).toBe("_0"); expect(identifierMap.get("b")).toBe("_1"); expect(identifierMap.get("c")).toBe("_2"); }); it("preserves entry point function names with #entrypoint", () => { const code = "#entrypoint \"myFunc\"\nfn myFunc() {}"; const tokens = tokenizeWgsl(code); const structMemberMap = collectStructNames(tokens); const { identifierMap } = collectDeclaredIdentifiersAndEntryPoints(tokens); expect(identifierMap.has("myFunc")).toBe(false); }); it("preserves main as entry point when no #entrypoint is specified", () => { const code = "fn main() {}"; const tokens = tokenizeWgsl(code); const structMemberMap = collectStructNames(tokens); const { identifierMap } = collectDeclaredIdentifiersAndEntryPoints(tokens); expect(identifierMap.has("main")).toBe(false); }); it("obfuscates non-entry point functions", () => { const code = "fn helper() {}"; const tokens = tokenizeWgsl(code); const structMemberMap = collectStructNames(tokens); const { identifierMap } = collectDeclaredIdentifiersAndEntryPoints(tokens); expect(identifierMap.get("helper")).toBe("_0"); }); it("maps function parameters in non-entry point functions", () => { const code = "fn myFunc(param1: i32, param2: f32) {}"; const tokens = tokenizeWgsl(code); const structMemberMap = collectStructNames(tokens); const { identifierMap } = collectDeclaredIdentifiersAndEntryPoints(tokens); expect(identifierMap.get("myFunc")).toBe("_0"); expect(identifierMap.get("param1")).toBe("_1"); expect(identifierMap.get("param2")).toBe("_2"); }); it("maps function parameters in entry point functions", () => { const code = "#entrypoint \"myFunc\"\nfn myFunc(param1: i32, param2: f32) {}"; const tokens = tokenizeWgsl(code); const structMemberMap = collectStructNames(tokens); const { identifierMap } = collectDeclaredIdentifiersAndEntryPoints(tokens); expect(identifierMap.has("myFunc")).toBe(false); expect(identifierMap.get("param1")).toBe("_0"); expect(identifierMap.get("param2")).toBe("_1"); }); it("maps struct type names", () => { const code = "struct MyStruct { x: f32 };"; const tokens = tokenizeWgsl(code); const structMemberMap = collectStructNames(tokens); const { identifierMap } = collectDeclaredIdentifiersAndEntryPoints(tokens); expect(identifierMap.get("MyStruct")).toBe("_0"); }); it("handles #binding directives", () => { const code = "#binding \"data\"\nlet data: i32;"; const tokens = tokenizeWgsl(code); const structMemberMap = collectStructNames(tokens); const { identifierMap, bindingMap } = collectDeclaredIdentifiersAndEntryPoints(tokens); expect(identifierMap.get("data")).toBe("_0"); expect(bindingMap.get("data")).toBe("_0"); }); it("handles multiple #entrypoint directives", () => { const code = "#entrypoint \"vertexMain\"\nfn vertexMain() {}\n#entrypoint \"fragmentMain\"\nfn fragmentMain() {}"; const tokens = tokenizeWgsl(code); const structMemberMap = collectStructNames(tokens); const { identifierMap } = collectDeclaredIdentifiersAndEntryPoints(tokens); expect(identifierMap.has("vertexMain")).toBe(false); expect(identifierMap.has("fragmentMain")).toBe(false); }); it("maps overlapping identifiers uniquely", () => { const code = "let x: i32 = 5; fn x(param: i32) {}"; const tokens = tokenizeWgsl(code); const structMemberMap = collectStructNames(tokens); const { identifierMap } = collectDeclaredIdentifiersAndEntryPoints(tokens); expect(identifierMap.size).toBe(2); expect(identifierMap.get("x")).toBe("_0"); expect(identifierMap.get("param")).toBe("_1"); }); it("integrates with collectStructNames for struct and variable declarations", () => { const code = "struct S { x: f32 }; let y: S;"; const tokens = tokenizeWgsl(code); const structMemberMap = collectStructNames(tokens); const { identifierMap } = collectDeclaredIdentifiersAndEntryPoints(tokens); expect(identifierMap.get("S")).toBe("_0"); expect(identifierMap.get("y")).toBe("_1"); }); }); describe("replaceIdentifiers", () => { // Token type (simplified for testing) interface Token { type: string; value: string; } // Create a single token function createToken(type: string, value: string): Token { return { type, value }; } // Simple tokenizer for test inputs (not full WGSL parsing) // function tokenizeSimple(code: string): Token[] { // const keywords = new Set(["struct", "fn", "let", "var", "const", "if", "else", "for", "while", "return"]); // const regex = /[\w]+|[^\s]/g; // const tokens: Token[] = []; // let match; // while ((match = regex.exec(code)) !== null) { // const value = match[0]; // const type = keywords.has(value) ? "keyword" : (value.match(/^[a-zA-Z_]/) ? "identifier" : "operator"); // tokens.push({ type, value }); // } // return tokens; // } // Convert tokens back to string for comparison function tokensToString(tokens: Token[]): string { return tokens.map(t => t.value).join(" "); } // General functionality tests it("replaces top-level identifiers using identifierMap", () => { const tokens = tokenizeWgsl("let a = 5"); const identifierMap = new Map<string, string>([["a", "_0"]]); const structMemberMap = new Map<string, string>(); const result = replaceIdentifiers(tokens, identifierMap, structMemberMap); expect(tokensToString(result)).toBe("let _0 = 5"); }); it("leaves identifiers not in either map unchanged", () => { const tokens = tokenizeWgsl("let unknown = 5"); const identifierMap = new Map<string, string>(); const structMemberMap = new Map<string, string>(); const result = replaceIdentifiers(tokens, identifierMap, structMemberMap); expect(tokensToString(result)).toBe("let unknown = 5"); }); it("preserves non-identifier tokens", () => { const tokens = tokenizeWgsl("let = ( ) ;"); const identifierMap = new Map<string, string>(); const structMemberMap = new Map<string, string>(); const result = replaceIdentifiers(tokens, identifierMap, structMemberMap); expect(tokensToString(result)).toBe("let = ( ) ;"); }); // Addressing failing test 1: Preserves vector swizzles it("preserves vector swizzles while obfuscating variables", () => { const input = "fn main ( ) { let v : vec3 < f32 > = vec3 < f32 > ( 1.0 , 2.0 , 3.0 ) ; let x = v . x ; let xy = v . xy ; let xyz = v . xyz ; }"; const tokens = tokenizeWgsl(input); const identifierMap = new Map<string, string>([ ["main", "_0"], ["v", "_1"], ["x", "_2"], ["xy", "_3"], ["xyz", "_4"] ]); const structMemberMap = new Map<string, string>(); // No structs in this test const result = replaceIdentifiers(tokens, identifierMap, structMemberMap); const expected = "fn _0 ( ) { let _1 : vec3 < f32 > = vec3 < f32 > ( 1.0 , 2.0 , 3.0 ) ; let _2 = _1 . x ; let _3 = _1 . xy ; let _4 = _1 . xyz ; }"; expect(tokensToString(result)).toBe(expected); }); // Addressing failing test 2: Obfuscates struct members, preserves swizzles it("obfuscates struct members while preserving vector swizzles", () => { const input = "struct S { x : f32 , y : i32 } ; fn main ( ) { let v : vec2 < f32 > = vec2 < f32 > ( 1.0 , 2.0 ) ; let s : S ; let a = v . x ; let b = s . y ; }"; const tokens = tokenizeWgsl(input); const identifierMap = new Map<string, string>([ ["S", "_0"], ["main", "_1"], ["v", "_2"], ["s", "_3"], ["a", "_4"], ["b", "_5"] ]); const structMemberMap = new Map<string, string>([]); const result = replaceIdentifiers(tokens, identifierMap, structMemberMap); const expected = "struct _0 { x : f32 , y : i32 } ; fn _1 ( ) { let _2 : vec2 < f32 > = vec2 < f32 > ( 1.0 , 2.0 ) ; let _3 : _0 ; let _4 = _2 . x ; let _5 = _3 . y ; }"; expect(tokensToString(result)).toBe(expected); }); // Addressing failing test 3: Mixed swizzles and struct members it("handles mixed vector swizzles and struct members with overlapping names", () => { const input = "struct S { xy : vec2 < f32 > , z : i32 } ; fn main ( ) { let v : vec3 < f32 > = vec3 < f32 > ( 1.0 , 2.0 , 3.0 ) ; let s : S ; let a = v . xy ; let b = v . z ; let c = s . z ; }"; const tokens = tokenizeWgsl(input); const identifierMap = new Map<string, string>([ ["S", "_0"], ["main", "_1"], ["v", "_2"], ["s", "_3"], ["a", "_4"], ["b", "_5"], ["c", "_6"] ]); const structMemberMap = new Map<string, string>([]); const result = replaceIdentifiers(tokens, identifierMap, structMemberMap); const expected = "struct _0 { xy : vec2 < f32 > , z : i32 } ; fn _1 ( ) { let _2 : vec3 < f32 > = vec3 < f32 > ( 1.0 , 2.0 , 3.0 ) ; let _3 : _0 ; let _4 = _2 . xy ; let _5 = _2 . z ; let _6 = _3 . z ; }"; expect(tokensToString(result)).toBe(expected); }); it("doesn't obfuscate color-based struct members in definitions", () => { const input = "struct Color { r : f32 , g : i32 , b : f32 }"; const tokens = tokenizeWgsl(input); const identifierMap = new Map<string, string>([["Color", "_0"]]); const structMemberMap = new Map<string, string>([]); const result = replaceIdentifiers(tokens, identifierMap, structMemberMap); const expected = "struct _0 { r : f32 , g : i32 , b : f32 }"; expect(tokensToString(result)).toBe(expected); }); it("doesn't obfuscates color-based struct members in accesses", () => { const input = "struct Color { r : f32 , g : i32 } ; fn main ( ) { let c : Color ; let red = c . r ; let green = c . g ; }"; const tokens = tokenizeWgsl(input); const identifierMap = new Map<string, string>([ ["Color", "_0"], ["main", "_1"], ["c", "_2"], ["red", "_3"], ["green", "_4"] ]); const structMemberMap = new Map<string, string>([ ]); const result = replaceIdentifiers(tokens, identifierMap, structMemberMap); const expected = "struct _0 { r : f32 , g : i32 } ; fn _1 ( ) { let _2 : _0 ; let _3 = _2 . r ; let _4 = _2 . g ; }"; expect(tokensToString(result)).toBe(expected); }); it("preserves color-based identifiers as invalid swizzles on vectors", () => { const input = "fn main ( ) { let v : vec3 < f32 > = vec3 < f32 > ( 1.0 , 2.0 , 3.0 ) ; let r = v . r ; let g = v . g ; }"; const tokens = tokenizeWgsl(input); const identifierMap = new Map<string, string>([ ["main", "_0"], ["v", "_1"], ["r", "_2"], ["g", "_3"] ]); const structMemberMap = new Map<string, string>(); // No struct members here const result = replaceIdentifiers(tokens, identifierMap, structMemberMap); const expected = "fn _0 ( ) { let _1 : vec3 < f32 > = vec3 < f32 > ( 1.0 , 2.0 , 3.0 ) ; let _2 = _1 . r ; let _3 = _1 . g ; }"; expect(tokensToString(result)).toBe(expected); }); // it("distinguishes color-based struct members from invalid swizzles", () => { // const input = "struct Color { r : f32 , g : i32 } ; fn main ( ) { let v : vec3 < f32 > ; let c : Color ; let vr = v . r ; let cr = c . r ; }"; // const tokens = tokenizeWgsl(input); // const identifierMap = new Map<string, string>([ // ["Color", "_0"], // ["main", "_1"], // ["v", "_2"], // ["c", "_3"], // ["vr", "_4"], // ["cr", "_5"] // ]); // const structMemberMap = new Map<string, string>([ // ["r", "_6"], // ["g", "_7"] // ]); // const result = replaceIdentifiers(tokens, identifierMap, structMemberMap); // const expected = "struct _0 { _6 : f32 , _7 : i32 } ; fn _1 ( ) { let _2 : vec3 < f32 > ; let _3 : _0 ; let _4 = _2 . r ; let _5 = _3 . _6 ; }"; // expect(tokensToString(result)).toBe(expected); // }); it("handles mixed swizzle-like and color-based struct members", () => { const input = "struct Mixed { x : f32 , r : i32 , rgb : vec3 < f32 > } ; fn main ( ) { let v : vec3 < f32 > ; let m : Mixed ; let vx = v . x ; let mr = m . r ; let mrgb = m . rgb ; }"; const tokens = tokenizeWgsl(input); const identifierMap = new Map<string, string>([ ["Mixed", "_0"], ["main", "_1"], ["v", "_2"], ["m", "_3"], ["vx", "_4"], ["mr", "_5"], ["mrgb", "_6"] ]); const structMemberMap = new Map<string, string>([ // ["x", "_7"], // ["r", "_8"], // ["rgb", "_9"] ]); const result = replaceIdentifiers(tokens, identifierMap, structMemberMap); const expected = "struct _0 { x : f32 , r : i32 , rgb : vec3 < f32 > } ; fn _1 ( ) { let _2 : vec3 < f32 > ; let _3 : _0 ; let _4 = _2 . x ; let _5 = _3 . r ; let _6 = _3 . rgb ; }"; expect(tokensToString(result)).toBe(expected); }); }); describe('WGSL Obfuscator', () => { it('renames identifiers while preserving keywords and attributes', () => { const input = `@fragment fn myFunc() { let x = 5; return; }`; const expected = `@fragment fn _0(){let _1=5;return;}`; expect(obfuscate(input)).toBe(expected); }); it('handles #binding directives correctly', () => { const input = `#binding "data"\nlet data = 42;`; const expected = `//#!binding data _0\nlet _0=42;`; expect(obfuscate(input)).toBe(expected); }); it('preserves operators and punctuation', () => { const input = `let a = 1 + 2 * 3;`; const expected = `let _0=1+2*3;`; expect(obfuscate(input)).toBe(expected); }); it('preserves string and number literals', () => { const input = `let s = "hello"; let n = 0xFF; let f = 3.14f;`; const expected = `let _0="hello";let _1=0xFF;let _2=3.14f;`; expect(obfuscate(input)).toBe(expected); }); it('removes comments', () => { const input = `// Single-line comment\nfn myFunc() { /* Multi-line\ncomment */ let x = 5; }`; const expected = `fn _0(){let _1=5;}`; expect(obfuscate(input)).toBe(expected); }); it('handles complex expressions', () => { const input = `fn process(x: f32, y: f32, z: f32, a: f32, b: f32){ let result = dot(vec3<f32>(1.0, 2.0, 3.0), normalize(vec3<f32>(x, y, z))) + 5.0 * length(vec2<f32>(a, b));}`; const expected = `fn _0(_1:f32,_2:f32,_3:f32,_4:f32,_5:f32){let _6=dot(vec3<f32>(1.0,2.0,3.0),normalize(vec3<f32>(_1,_2,_3)))+5.0*length(vec2<f32>(_4,_5));}`; expect(obfuscate(input)).toBe(expected); }); it('handles struct definitions', () => { const input = `struct MyStruct { field1: i32; field2: vec2<f32>; };`; const expected = `struct _2{_0:i32;_1:vec2<f32>;};`; expect(obfuscate(input)).toBe(expected); }); it('handles attributes with parameters', () => { // This test could perhaps be improved. // I think there's a rendundant space after (position). const input = `fn myFunc(@builtin(position) var<out> gl_Position: vec4<f32>){}`; const expected = `fn _0(@builtin(position) var<out>_1:vec4<f32>){}`; expect(obfuscate(input)).toBe(expected); }); it('handles switch statements', () => { const input = `let x = 1; var y = 0;\nswitch (x) { case 1: { y = 10; } case 2, 3: { y = 20; } default: { y = 0; } }}`; const expected = `let _0=1;var _1=0;switch(_0){case 1:{_1=10;}case 2,3:{_1=20;}default:{_1=0;}}}`; expect(obfuscate(input)).toBe(expected); }); it('handles functions with parameters', () => { const input = `fn add(a: i32, b: i32) -> i32 { return a + b; }`; const expected = `fn _0(_1:i32,_2:i32)->i32{return _1+_2;}`; expect(obfuscate(input)).toBe(expected); }); it('handles bitwise operations', () => { const input = `let x = 1 | 2 & 3 ^ 4;`; const expected = `let _0=1|2&3^4;`; expect(obfuscate(input)).toBe(expected); }); it('handles minimal whitespace', () => { const input = `fn myFunc(){let x=5;return;}`; const expected = `fn _0(){let _1=5;return;}`; expect(obfuscate(input)).toBe(expected); }); it('handles excessive whitespace', () => { const input = `fn myFunc( ) { let x = 5 ; return ; }`; const expected = `fn _0(){let _1=5;return;}`; expect(obfuscate(input)).toBe(expected); }); it('preserves built-in functions and types', () => { const input = `let v = vec3<f32>(1.0, 2.0, 3.0); let m = mat2x2<f32>();`; const expected = `let _0=vec3<f32>(1.0,2.0,3.0);let _1=mat2x2<f32>();`; expect(obfuscate(input)).toBe(expected); }); it('handles multiple #binding directives', () => { const input = `#binding "data"\n#binding "buffer"\nlet data = 42; let buffer = 24;`; const expected = `//#!binding data _0\n//#!binding buffer _1\nlet _0=42;let _1=24;`; expect(obfuscate(input)).toBe(expected); }); it('preserves single entry point function specified by #entrypoint', () => { const input = ` #entrypoint "myCompute" @compute @workgroup_size(1,1) fn myCompute() { let x = 1; } fn helper() { var y = 2; } `; const expected = `@compute @workgroup_size(1,1) fn myCompute(){let _0=1;}fn _1(){var _2=2;}`; expect(obfuscate(input)).toBe(expected); }); it('preserves main as entry point when no #entrypoint is specified', () => { const input = ` @compute @workgroup_size(1,1) fn main() { let a = 5; } fn otherFunc() { var b = 10; } `; const expected = `@compute @workgroup_size(1,1) fn main(){let _0=5;}fn _1(){var _2=10;}`; expect(obfuscate(input)).toBe(expected); }); it('preserves multiple entry point functions with #entrypoint directives', () => { const input = ` #entrypoint "vertexMain" #entrypoint "fragmentMain" @vertex fn vertexMain() { let v = 1; } @fragment fn fragmentMain() { let f = 2; } fn utility() { var u = 3; } `; const expected = `@vertex fn vertexMain(){let _0=1;}@fragment fn fragmentMain(){let _1=2;}fn _2(){var _3=3;}`; expect(obfuscate(input)).toBe(expected); }); it('obfuscates non-main entry point when no #entrypoint is specified', () => { const input = ` @compute @workgroup_size(1,1) fn customEntry() { let z = 4; } fn support() { var s = 5; } `; const expected = `@compute @workgroup_size(1,1) fn _0(){let _1=4;}fn _2(){var _3=5;}`; expect(obfuscate(input)).toBe(expected); }); it('handles mixed #entrypoint and #binding directives', () => { const input = ` #entrypoint "computeMain" #binding "data" @compute @workgroup_size(1,1) fn computeMain() { let x = data; } `; const expected = ` //#!binding data _0 @compute @workgroup_size(1,1) fn computeMain(){let _1=_0;} `.trim(); expect(obfuscate(input)).toBe(expected); }); it('preserves vector swizzles while obfuscating variables', () => { const input = ` fn main() { let v: vec3<f32> = vec3<f32>(1.0, 2.0, 3.0); let x = v.x; let xy = v.xy; let xyz = v.xyz; } `; const expected = `fn main(){let _0:vec3<f32>=vec3<f32>(1.0,2.0,3.0);let _1=_0.x;let _2=_0.xy;let _3=_0.xyz;}`; expect(obfuscate(input)).toBe(expected); }); it('obfuscates struct members while preserving vector swizzles', () => { // This test has been defunct since now we are just not renaming potential swizzler names. const input = ` struct MyStruct { x: f32, y: i32 }; fn main() { let v: vec2<f32> = vec2<f32>(1.0, 2.0); let s: MyStruct; let a = v.x; let b = s.x; let c = s.y; } `; const expected = `struct _0{x:f32,y:i32};fn main(){let _1:vec2<f32>=vec2<f32>(1.0,2.0);let _2:_0;let _3=_1.x;let _4=_2.x;let _5=_2.y;}`; expect(obfuscate(input)).toBe(expected); }); it('handles mixed vector swizzles and struct members with overlapping names', () => { const input = ` struct Position { xy: vec2<f32>, z: i32 }; fn main() { let v: vec3<f32> = vec3<f32>(1.0, 2.0, 3.0); let p: Position; let swizzle1 = v.xy; let swizzle2 = v.z; let member1 = p.xy; let member2 = p.z; } `; const expected = `struct _0{xy:vec2<f32>,z:i32};fn main(){let _1:vec3<f32>=vec3<f32>(1.0,2.0,3.0);let _2:_0;let _3=_1.xy;let _4=_1.z;let _5=_2.xy;let _6=_2.z;}`; expect(obfuscate(input)).toBe(expected); }); });