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
text/typescript
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);
});
});