From 6262d7afcf1a1a6653d5cddedd417c40d6ade1e1 Mon Sep 17 00:00:00 2001 From: Aleksander Katan Date: Fri, 27 Feb 2026 17:21:16 +0100 Subject: [PATCH 01/13] Add a base implementation of parameter assign --- .../src/rules/invalidAssignment.ts | 52 ++++++++++++++ packages/eslint-plugin/src/utils/nodeUtils.ts | 20 ++++++ .../tests/invalidAssignment.test.ts | 68 +++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 packages/eslint-plugin/src/rules/invalidAssignment.ts create mode 100644 packages/eslint-plugin/src/utils/nodeUtils.ts create mode 100644 packages/eslint-plugin/tests/invalidAssignment.test.ts diff --git a/packages/eslint-plugin/src/rules/invalidAssignment.ts b/packages/eslint-plugin/src/rules/invalidAssignment.ts new file mode 100644 index 0000000000..7ef1887cbf --- /dev/null +++ b/packages/eslint-plugin/src/rules/invalidAssignment.ts @@ -0,0 +1,52 @@ +import { ASTUtils, type TSESTree } from '@typescript-eslint/utils'; +import { createRule } from '../ruleCreator.ts'; +import { enhanceRule } from '../enhanceRule.ts'; +import { directiveTracking } from '../enhancers/directiveTracking.ts'; +import { getBaseFromAccessChain } from '../utils/nodeUtils.ts'; + +export const invalidAssignment = createRule({ + name: 'invalid-assignment', + meta: { + type: 'problem', + docs: { + description: `Avoid assignments that will not generate valid WGSL.`, + }, + messages: { + parameterAssignment: + "Cannot assign to '{{snippet}}' since WGSL parameters are immutable.", + }, + schema: [], + }, + defaultOptions: [], + + create: enhanceRule({ directives: directiveTracking }, (context, state) => { + const { directives } = state; + + return { + AssignmentExpression(node) { + if (!directives.insideUseGpu()) { + return; + } + + // look for the definition of the variable we assign to + const assignee = getBaseFromAccessChain(node.left); + const variable = ASTUtils.findVariable( + context.sourceCode.getScope(assignee), + assignee.name, + ); + if (variable && variable.defs.length > 0) { + const def = variable.defs[0]; // first definition in this scope + + // was it defined as a parameter? + if (def?.type === 'Parameter') { + context.report({ + messageId: 'parameterAssignment', + node, + data: { snippet: context.sourceCode.getText(node.left) }, + }); + } + } + }, + }; + }), +}); diff --git a/packages/eslint-plugin/src/utils/nodeUtils.ts b/packages/eslint-plugin/src/utils/nodeUtils.ts new file mode 100644 index 0000000000..68039e4274 --- /dev/null +++ b/packages/eslint-plugin/src/utils/nodeUtils.ts @@ -0,0 +1,20 @@ +import { TSESTree } from '@typescript-eslint/utils'; + +/** + * Retrieves the base object from a property access chain. + * Also considers array access. + * + * @example + * // for simplicity, using code snippets instead of ASTs + * getBaseFromAccessChain('a'); // a + * getBaseFromAccessChain('d.u32'); // d + * getBaseFromAccessChain('myObj.prop.prop'); // myObj + * getBaseFromAccessChain("myObj['prop']""); // myObj TODO: actually implement this + */ +export function getBaseFromAccessChain(node: TSESTree.Node) { + let result: TSESTree.Node = node; + while (result.type === 'MemberExpression') { + result = result.object; + } + return result; +} diff --git a/packages/eslint-plugin/tests/invalidAssignment.test.ts b/packages/eslint-plugin/tests/invalidAssignment.test.ts new file mode 100644 index 0000000000..fa14882812 --- /dev/null +++ b/packages/eslint-plugin/tests/invalidAssignment.test.ts @@ -0,0 +1,68 @@ +import { describe } from 'vitest'; +import { ruleTester } from './ruleTester.ts'; +import { invalidAssignment } from '../src/rules/invalidAssignment.ts'; + +// TODO: non-param assign +// TODO: +=, ++, -- etc +// TODO: array access +// TODO: props +// TODO: check default params +// TODO: allow js +// TODO: allow .$ (?) +describe('invalidAssignment', () => { + ruleTester.run('parameterAssignment', invalidAssignment, { + valid: [ + 'const fn = (a) => { a = {}; a.prop = 1; }', + "const fn = (a) => { 'use gpu'; let x = 0; x = 1; }", + "const fn = (a) => { 'use gpu'; { let a = 1; a = 2; } }", + ], + invalid: [ + { + code: "const fn = (a) => { 'use gpu'; a = 1; }", + errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], + }, + { + code: "let a; const fn = (a) => { 'use gpu'; a = 1; }", + errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], + }, + { + code: "const fn = (a) => { 'use gpu'; a.x = 1; }", + errors: [{ + messageId: 'parameterAssignment', + data: { snippet: 'a.x' }, + }], + }, + { + code: "const fn = (a) => { 'use gpu'; a.x.y = 1; }", + errors: [{ + messageId: 'parameterAssignment', + data: { snippet: 'a.x.y' }, + }], + }, + { + code: "const fn = (a) => { 'use gpu'; if (true) { a = 1; } }", + errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], + }, + { + code: "const fn = (a, b) => { 'use gpu'; a = 1; b = 2; }", + errors: [ + { messageId: 'parameterAssignment', data: { snippet: 'a' } }, + { messageId: 'parameterAssignment', data: { snippet: 'b' } }, + ], + }, + { + code: + "const outer = (a) => { 'use gpu'; const inner = (b) => { 'use gpu'; b = 1; }; }", + errors: [{ messageId: 'parameterAssignment', data: { snippet: 'b' } }], + }, + { + code: "const fn = (a) => { 'use gpu'; a = 1; { let a; } }", + errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], + }, + { + code: "const fn = (a) => { 'use gpu'; a = 1; let a; }", + errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], + }, + ], + }); +}); From 77f352437014fc09105886d426265046dac6a3db Mon Sep 17 00:00:00 2001 From: Aleksander Katan Date: Fri, 27 Feb 2026 17:26:39 +0100 Subject: [PATCH 02/13] Add rule to config --- packages/eslint-plugin/src/configs.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/eslint-plugin/src/configs.ts b/packages/eslint-plugin/src/configs.ts index e8a2e8d387..31eb3e62b9 100644 --- a/packages/eslint-plugin/src/configs.ts +++ b/packages/eslint-plugin/src/configs.ts @@ -1,10 +1,12 @@ import type { TSESLint } from '@typescript-eslint/utils'; import { integerDivision } from './rules/integerDivision.ts'; import { unwrappedPojos } from './rules/unwrappedPojos.ts'; +import { invalidAssignment } from './rules/invalidAssignment.ts'; export const rules = { 'integer-division': integerDivision, 'unwrapped-pojo': unwrappedPojos, + 'invalid-assignment': invalidAssignment, } as const; type Rules = Record< @@ -15,9 +17,11 @@ type Rules = Record< export const recommendedRules: Rules = { 'typegpu/integer-division': 'warn', 'typegpu/unwrapped-pojo': 'warn', + 'typegpu/invalid-assignment': 'warn', }; export const allRules: Rules = { 'typegpu/integer-division': 'error', 'typegpu/unwrapped-pojo': 'error', + 'typegpu/invalid-assignment': 'error', }; From 49a69b6d80855f0928dec34e942a09c647e0dd44 Mon Sep 17 00:00:00 2001 From: Aleksander Katan Date: Fri, 27 Feb 2026 17:37:09 +0100 Subject: [PATCH 03/13] Add comments, fix types --- .../src/rules/invalidAssignment.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/invalidAssignment.ts b/packages/eslint-plugin/src/rules/invalidAssignment.ts index 7ef1887cbf..69047ffee6 100644 --- a/packages/eslint-plugin/src/rules/invalidAssignment.ts +++ b/packages/eslint-plugin/src/rules/invalidAssignment.ts @@ -30,15 +30,23 @@ export const invalidAssignment = createRule({ // look for the definition of the variable we assign to const assignee = getBaseFromAccessChain(node.left); - const variable = ASTUtils.findVariable( + if (assignee.type !== 'Identifier') { + return; + } + + // look for a scope that contains at least one + const defs = ASTUtils.findVariable( context.sourceCode.getScope(assignee), assignee.name, - ); - if (variable && variable.defs.length > 0) { - const def = variable.defs[0]; // first definition in this scope + )?.defs; + + if (defs && defs.length > 0) { + // def[0] points to the correct scope + // defs is an array because there may be multiple definitions with `var` + const def = defs[0]; - // was it defined as a parameter? if (def?.type === 'Parameter') { + // either 'use gpu' or other parameter, either way invalid context.report({ messageId: 'parameterAssignment', node, From 3027d648282635cc5b8b07098f2e9ad631eacd98 Mon Sep 17 00:00:00 2001 From: Aleksander Katan Date: Fri, 27 Feb 2026 17:57:29 +0100 Subject: [PATCH 04/13] Allow assignments with dollar --- .../src/rules/invalidAssignment.ts | 15 +++++++++++--- packages/eslint-plugin/src/utils/nodeUtils.ts | 20 ------------------- .../tests/invalidAssignment.test.ts | 1 + 3 files changed, 13 insertions(+), 23 deletions(-) delete mode 100644 packages/eslint-plugin/src/utils/nodeUtils.ts diff --git a/packages/eslint-plugin/src/rules/invalidAssignment.ts b/packages/eslint-plugin/src/rules/invalidAssignment.ts index 69047ffee6..f30970a2f2 100644 --- a/packages/eslint-plugin/src/rules/invalidAssignment.ts +++ b/packages/eslint-plugin/src/rules/invalidAssignment.ts @@ -2,7 +2,6 @@ import { ASTUtils, type TSESTree } from '@typescript-eslint/utils'; import { createRule } from '../ruleCreator.ts'; import { enhanceRule } from '../enhanceRule.ts'; import { directiveTracking } from '../enhancers/directiveTracking.ts'; -import { getBaseFromAccessChain } from '../utils/nodeUtils.ts'; export const invalidAssignment = createRule({ name: 'invalid-assignment', @@ -13,7 +12,7 @@ export const invalidAssignment = createRule({ }, messages: { parameterAssignment: - "Cannot assign to '{{snippet}}' since WGSL parameters are immutable.", + "Cannot assign to '{{snippet}}' since WGSL parameters are immutable. If you're using d.ref, please either use '.$' or disable this rule.", }, schema: [], }, @@ -29,7 +28,17 @@ export const invalidAssignment = createRule({ } // look for the definition of the variable we assign to - const assignee = getBaseFromAccessChain(node.left); + let assignee = node.left; + while (assignee.type === 'MemberExpression') { + if ( + assignee.property.type === 'Identifier' && + assignee.property.name === '$' + ) { + // a dollar was used so we assume this assignment is fine + return; + } + assignee = assignee.object; + } if (assignee.type !== 'Identifier') { return; } diff --git a/packages/eslint-plugin/src/utils/nodeUtils.ts b/packages/eslint-plugin/src/utils/nodeUtils.ts deleted file mode 100644 index 68039e4274..0000000000 --- a/packages/eslint-plugin/src/utils/nodeUtils.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TSESTree } from '@typescript-eslint/utils'; - -/** - * Retrieves the base object from a property access chain. - * Also considers array access. - * - * @example - * // for simplicity, using code snippets instead of ASTs - * getBaseFromAccessChain('a'); // a - * getBaseFromAccessChain('d.u32'); // d - * getBaseFromAccessChain('myObj.prop.prop'); // myObj - * getBaseFromAccessChain("myObj['prop']""); // myObj TODO: actually implement this - */ -export function getBaseFromAccessChain(node: TSESTree.Node) { - let result: TSESTree.Node = node; - while (result.type === 'MemberExpression') { - result = result.object; - } - return result; -} diff --git a/packages/eslint-plugin/tests/invalidAssignment.test.ts b/packages/eslint-plugin/tests/invalidAssignment.test.ts index fa14882812..122a7930f8 100644 --- a/packages/eslint-plugin/tests/invalidAssignment.test.ts +++ b/packages/eslint-plugin/tests/invalidAssignment.test.ts @@ -15,6 +15,7 @@ describe('invalidAssignment', () => { 'const fn = (a) => { a = {}; a.prop = 1; }', "const fn = (a) => { 'use gpu'; let x = 0; x = 1; }", "const fn = (a) => { 'use gpu'; { let a = 1; a = 2; } }", + "const fn = (a) => { 'use gpu'; a.$ = 1 }", ], invalid: [ { From c53f113737c18ca3f3dbe9b7011c16d8877cbf56 Mon Sep 17 00:00:00 2001 From: Aleksander Katan Date: Mon, 2 Mar 2026 10:32:41 +0100 Subject: [PATCH 05/13] Add array access tests --- .../tests/invalidAssignment.test.ts | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/tests/invalidAssignment.test.ts b/packages/eslint-plugin/tests/invalidAssignment.test.ts index 122a7930f8..3bd8a93c36 100644 --- a/packages/eslint-plugin/tests/invalidAssignment.test.ts +++ b/packages/eslint-plugin/tests/invalidAssignment.test.ts @@ -4,15 +4,15 @@ import { invalidAssignment } from '../src/rules/invalidAssignment.ts'; // TODO: non-param assign // TODO: +=, ++, -- etc -// TODO: array access -// TODO: props // TODO: check default params // TODO: allow js -// TODO: allow .$ (?) describe('invalidAssignment', () => { ruleTester.run('parameterAssignment', invalidAssignment, { valid: [ - 'const fn = (a) => { a = {}; a.prop = 1; }', + 'const fn = (a) => { a = {}; }', + 'const fn = (a) => { a.prop = 1; }', + "const fn = (a) => { a['prop'] = 1; }", + 'const fn = (a) => { a[0] = 1; }', "const fn = (a) => { 'use gpu'; let x = 0; x = 1; }", "const fn = (a) => { 'use gpu'; { let a = 1; a = 2; } }", "const fn = (a) => { 'use gpu'; a.$ = 1 }", @@ -33,6 +33,20 @@ describe('invalidAssignment', () => { data: { snippet: 'a.x' }, }], }, + { + code: "const fn = (a) => { 'use gpu'; a['prop'] = 1; }", + errors: [{ + messageId: 'parameterAssignment', + data: { snippet: "a['prop']" }, + }], + }, + { + code: "const fn = (a) => { 'use gpu'; a[0] = 1; }", + errors: [{ + messageId: 'parameterAssignment', + data: { snippet: 'a[0]' }, + }], + }, { code: "const fn = (a) => { 'use gpu'; a.x.y = 1; }", errors: [{ @@ -64,6 +78,13 @@ describe('invalidAssignment', () => { code: "const fn = (a) => { 'use gpu'; a = 1; let a; }", errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], }, + { + code: "const fn = (a) => { 'use gpu'; a.$prop = 1; }", + errors: [{ + messageId: 'parameterAssignment', + data: { snippet: 'a.$prop' }, + }], + }, ], }); }); From bddd512177f06c94a0c4b8f1f7448d3460959393 Mon Sep 17 00:00:00 2001 From: Aleksander Katan Date: Mon, 2 Mar 2026 11:07:38 +0100 Subject: [PATCH 06/13] Handle update expression --- .../src/rules/invalidAssignment.ts | 43 +++++++++++++++++++ .../tests/invalidAssignment.test.ts | 25 ++++++----- 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/packages/eslint-plugin/src/rules/invalidAssignment.ts b/packages/eslint-plugin/src/rules/invalidAssignment.ts index f30970a2f2..2ce0227efe 100644 --- a/packages/eslint-plugin/src/rules/invalidAssignment.ts +++ b/packages/eslint-plugin/src/rules/invalidAssignment.ts @@ -22,6 +22,49 @@ export const invalidAssignment = createRule({ const { directives } = state; return { + UpdateExpression(node) { + if (!directives.insideUseGpu()) { + return; + } + + // look for the definition of the variable we assign to + let assignee = node.argument; + while (assignee.type === 'MemberExpression') { + if ( + assignee.property.type === 'Identifier' && + assignee.property.name === '$' + ) { + // a dollar was used so we assume this assignment is fine + return; + } + assignee = assignee.object; + } + if (assignee.type !== 'Identifier') { + return; + } + + // look for a scope that contains at least one + const defs = ASTUtils.findVariable( + context.sourceCode.getScope(assignee), + assignee.name, + )?.defs; + + if (defs && defs.length > 0) { + // def[0] points to the correct scope + // defs is an array because there may be multiple definitions with `var` + const def = defs[0]; + + if (def?.type === 'Parameter') { + // either 'use gpu' or other parameter, either way invalid + context.report({ + messageId: 'parameterAssignment', + node, + data: { snippet: context.sourceCode.getText(node.argument) }, + }); + } + } + }, + AssignmentExpression(node) { if (!directives.insideUseGpu()) { return; diff --git a/packages/eslint-plugin/tests/invalidAssignment.test.ts b/packages/eslint-plugin/tests/invalidAssignment.test.ts index 3bd8a93c36..f62431b029 100644 --- a/packages/eslint-plugin/tests/invalidAssignment.test.ts +++ b/packages/eslint-plugin/tests/invalidAssignment.test.ts @@ -3,9 +3,6 @@ import { ruleTester } from './ruleTester.ts'; import { invalidAssignment } from '../src/rules/invalidAssignment.ts'; // TODO: non-param assign -// TODO: +=, ++, -- etc -// TODO: check default params -// TODO: allow js describe('invalidAssignment', () => { ruleTester.run('parameterAssignment', invalidAssignment, { valid: [ @@ -16,21 +13,31 @@ describe('invalidAssignment', () => { "const fn = (a) => { 'use gpu'; let x = 0; x = 1; }", "const fn = (a) => { 'use gpu'; { let a = 1; a = 2; } }", "const fn = (a) => { 'use gpu'; a.$ = 1 }", + "const fn = (a) => { 'use gpu'; a.$++; }", + "const fn = (a) => { 'use gpu'; a.$ += 1; }", ], invalid: [ { code: "const fn = (a) => { 'use gpu'; a = 1; }", errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], }, + { + code: "const fn = (a) => { 'use gpu'; a++; }", + errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], + }, + { + code: "const fn = (a) => { 'use gpu'; a += 1; }", + errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], + }, { code: "let a; const fn = (a) => { 'use gpu'; a = 1; }", errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], }, { - code: "const fn = (a) => { 'use gpu'; a.x = 1; }", + code: "const fn = (a) => { 'use gpu'; a.prop = 1; }", errors: [{ messageId: 'parameterAssignment', - data: { snippet: 'a.x' }, + data: { snippet: 'a.prop' }, }], }, { @@ -48,10 +55,10 @@ describe('invalidAssignment', () => { }], }, { - code: "const fn = (a) => { 'use gpu'; a.x.y = 1; }", + code: "const fn = (a) => { 'use gpu'; a.prop1.prop2 = 1; }", errors: [{ messageId: 'parameterAssignment', - data: { snippet: 'a.x.y' }, + data: { snippet: 'a.prop1.prop2' }, }], }, { @@ -74,10 +81,6 @@ describe('invalidAssignment', () => { code: "const fn = (a) => { 'use gpu'; a = 1; { let a; } }", errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], }, - { - code: "const fn = (a) => { 'use gpu'; a = 1; let a; }", - errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], - }, { code: "const fn = (a) => { 'use gpu'; a.$prop = 1; }", errors: [{ From 23a53b1f46c3a670f6b811a95f82a20a02fef925 Mon Sep 17 00:00:00 2001 From: Aleksander Katan Date: Mon, 2 Mar 2026 11:32:45 +0100 Subject: [PATCH 07/13] Update directiveTracking to hold function node --- .../src/enhancers/directiveTracking.ts | 25 ++++++++++++++----- .../src/rules/invalidAssignment.ts | 4 +-- .../eslint-plugin/src/rules/unwrappedPojos.ts | 2 +- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/eslint-plugin/src/enhancers/directiveTracking.ts b/packages/eslint-plugin/src/enhancers/directiveTracking.ts index d8efde55d0..b490760afb 100644 --- a/packages/eslint-plugin/src/enhancers/directiveTracking.ts +++ b/packages/eslint-plugin/src/enhancers/directiveTracking.ts @@ -2,8 +2,13 @@ import type { TSESTree } from '@typescript-eslint/utils'; import type { RuleListener } from '@typescript-eslint/utils/ts-eslint'; import type { RuleEnhancer } from '../enhanceRule.ts'; +export type FunctionNode = + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.ArrowFunctionExpression; + export type DirectiveData = { - insideUseGpu: () => boolean; + getEnclosingTypegpuFunction: () => FunctionNode | undefined; }; /** @@ -16,17 +21,17 @@ export type DirectiveData = { * - top level directives. */ export const directiveTracking: RuleEnhancer = () => { - const stack: string[][] = []; + const stack: { node: FunctionNode; directives: string[] }[] = []; const visitors: RuleListener = { FunctionDeclaration(node) { - stack.push(getDirectives(node)); + stack.push({ node, directives: getDirectives(node) }); }, FunctionExpression(node) { - stack.push(getDirectives(node)); + stack.push({ node, directives: getDirectives(node) }); }, ArrowFunctionExpression(node) { - stack.push(getDirectives(node)); + stack.push({ node, directives: getDirectives(node) }); }, 'FunctionDeclaration:exit'() { @@ -42,7 +47,15 @@ export const directiveTracking: RuleEnhancer = () => { return { visitors, - state: { insideUseGpu: () => (stack.at(-1) ?? []).includes('use gpu') }, + state: { + getEnclosingTypegpuFunction: () => { + const current = stack.at(-1); + if (current && current.directives.includes('use gpu')) { + return current.node; + } + return undefined; + }, + }, }; }; diff --git a/packages/eslint-plugin/src/rules/invalidAssignment.ts b/packages/eslint-plugin/src/rules/invalidAssignment.ts index 2ce0227efe..a77dd4f238 100644 --- a/packages/eslint-plugin/src/rules/invalidAssignment.ts +++ b/packages/eslint-plugin/src/rules/invalidAssignment.ts @@ -23,7 +23,7 @@ export const invalidAssignment = createRule({ return { UpdateExpression(node) { - if (!directives.insideUseGpu()) { + if (!directives.getEnclosingTypegpuFunction()) { return; } @@ -66,7 +66,7 @@ export const invalidAssignment = createRule({ }, AssignmentExpression(node) { - if (!directives.insideUseGpu()) { + if (!directives.getEnclosingTypegpuFunction()) { return; } diff --git a/packages/eslint-plugin/src/rules/unwrappedPojos.ts b/packages/eslint-plugin/src/rules/unwrappedPojos.ts index babe1a3f8b..f2d6900076 100644 --- a/packages/eslint-plugin/src/rules/unwrappedPojos.ts +++ b/packages/eslint-plugin/src/rules/unwrappedPojos.ts @@ -22,7 +22,7 @@ export const unwrappedPojos = createRule({ return { ObjectExpression(node) { - if (!directives.insideUseGpu()) { + if (!directives.getEnclosingTypegpuFunction()) { return; } if (node.parent?.type === 'Property') { From 397b9cbc995484698fe67664449b5555acc5c29b Mon Sep 17 00:00:00 2001 From: Aleksander Katan Date: Mon, 2 Mar 2026 11:50:47 +0100 Subject: [PATCH 08/13] Handle JS assignment --- .../src/rules/invalidAssignment.ts | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/src/rules/invalidAssignment.ts b/packages/eslint-plugin/src/rules/invalidAssignment.ts index a77dd4f238..d3e75c0dc3 100644 --- a/packages/eslint-plugin/src/rules/invalidAssignment.ts +++ b/packages/eslint-plugin/src/rules/invalidAssignment.ts @@ -13,6 +13,8 @@ export const invalidAssignment = createRule({ messages: { parameterAssignment: "Cannot assign to '{{snippet}}' since WGSL parameters are immutable. If you're using d.ref, please either use '.$' or disable this rule.", + jsAssignment: + "Cannot assign to '{{snippet}}' since it is a JS variable defined outside of the current TypeGPU function's scope. Use buffers, workgroup variables or local variables instead.", }, schema: [], }, @@ -66,7 +68,8 @@ export const invalidAssignment = createRule({ }, AssignmentExpression(node) { - if (!directives.getEnclosingTypegpuFunction()) { + const enclosingFn = directives.getEnclosingTypegpuFunction(); + if (!enclosingFn) { return; } @@ -87,17 +90,30 @@ export const invalidAssignment = createRule({ } // look for a scope that contains at least one - const defs = ASTUtils.findVariable( + const variable = ASTUtils.findVariable( context.sourceCode.getScope(assignee), assignee.name, - )?.defs; + ); - if (defs && defs.length > 0) { + // TODO: handle variables with no defs (globalThis, Math etc.) + if (variable?.defs && variable.defs[0]) { // def[0] points to the correct scope // defs is an array because there may be multiple definitions with `var` - const def = defs[0]; + const def = variable.defs[0]; - if (def?.type === 'Parameter') { + // we check if it was defined outside of current function by checking ranges + if ( + def.node.range[1] < enclosingFn.range[0] || + enclosingFn.range[1] < def.node.range[0] + ) { + context.report({ + messageId: 'jsAssignment', + node, + data: { snippet: context.sourceCode.getText(node.left) }, + }); + } + + if (def.type === 'Parameter') { // either 'use gpu' or other parameter, either way invalid context.report({ messageId: 'parameterAssignment', From 4085f20544616005189b2c54406c7aa591ae6aca Mon Sep 17 00:00:00 2001 From: Aleksander Katan Date: Mon, 2 Mar 2026 11:56:56 +0100 Subject: [PATCH 09/13] Reduce code duplication --- .../src/rules/invalidAssignment.ts | 156 +++++++----------- 1 file changed, 64 insertions(+), 92 deletions(-) diff --git a/packages/eslint-plugin/src/rules/invalidAssignment.ts b/packages/eslint-plugin/src/rules/invalidAssignment.ts index d3e75c0dc3..5e6be2c6ee 100644 --- a/packages/eslint-plugin/src/rules/invalidAssignment.ts +++ b/packages/eslint-plugin/src/rules/invalidAssignment.ts @@ -2,6 +2,7 @@ import { ASTUtils, type TSESTree } from '@typescript-eslint/utils'; import { createRule } from '../ruleCreator.ts'; import { enhanceRule } from '../enhanceRule.ts'; import { directiveTracking } from '../enhancers/directiveTracking.ts'; +import { RuleContext } from '@typescript-eslint/utils/ts-eslint'; export const invalidAssignment = createRule({ name: 'invalid-assignment', @@ -25,104 +26,75 @@ export const invalidAssignment = createRule({ return { UpdateExpression(node) { - if (!directives.getEnclosingTypegpuFunction()) { - return; - } - - // look for the definition of the variable we assign to - let assignee = node.argument; - while (assignee.type === 'MemberExpression') { - if ( - assignee.property.type === 'Identifier' && - assignee.property.name === '$' - ) { - // a dollar was used so we assume this assignment is fine - return; - } - assignee = assignee.object; - } - if (assignee.type !== 'Identifier') { - return; - } - - // look for a scope that contains at least one - const defs = ASTUtils.findVariable( - context.sourceCode.getScope(assignee), - assignee.name, - )?.defs; - - if (defs && defs.length > 0) { - // def[0] points to the correct scope - // defs is an array because there may be multiple definitions with `var` - const def = defs[0]; - - if (def?.type === 'Parameter') { - // either 'use gpu' or other parameter, either way invalid - context.report({ - messageId: 'parameterAssignment', - node, - data: { snippet: context.sourceCode.getText(node.argument) }, - }); - } - } + const enclosingFn = directives.getEnclosingTypegpuFunction(); + checkAssignment(context, node, enclosingFn, node.argument); }, AssignmentExpression(node) { const enclosingFn = directives.getEnclosingTypegpuFunction(); - if (!enclosingFn) { - return; - } + checkAssignment(context, node, enclosingFn, node.left); + }, + }; + }), +}); - // look for the definition of the variable we assign to - let assignee = node.left; - while (assignee.type === 'MemberExpression') { - if ( - assignee.property.type === 'Identifier' && - assignee.property.name === '$' - ) { - // a dollar was used so we assume this assignment is fine - return; - } - assignee = assignee.object; - } - if (assignee.type !== 'Identifier') { - return; - } +function checkAssignment( + context: Readonly>, + node: TSESTree.Node, + enclosingFn: TSESTree.Node | undefined, + leftNode: TSESTree.Node, +) { + if (!enclosingFn) { + return; + } - // look for a scope that contains at least one - const variable = ASTUtils.findVariable( - context.sourceCode.getScope(assignee), - assignee.name, - ); + // look for the definition of the variable we assign to + let assignee = leftNode; + while (assignee.type === 'MemberExpression') { + if ( + assignee.property.type === 'Identifier' && + assignee.property.name === '$' + ) { + // a dollar was used so we assume this assignment is fine + return; + } + assignee = assignee.object; + } + if (assignee.type !== 'Identifier') { + return; + } - // TODO: handle variables with no defs (globalThis, Math etc.) - if (variable?.defs && variable.defs[0]) { - // def[0] points to the correct scope - // defs is an array because there may be multiple definitions with `var` - const def = variable.defs[0]; + // look for a scope that contains at least one + const variable = ASTUtils.findVariable( + context.sourceCode.getScope(assignee), + assignee.name, + ); - // we check if it was defined outside of current function by checking ranges - if ( - def.node.range[1] < enclosingFn.range[0] || - enclosingFn.range[1] < def.node.range[0] - ) { - context.report({ - messageId: 'jsAssignment', - node, - data: { snippet: context.sourceCode.getText(node.left) }, - }); - } + // TODO: handle variables with no defs (globalThis, Math etc.) + if (variable?.defs && variable.defs[0]) { + // def[0] points to the correct scope + // defs is an array because there may be multiple definitions with `var` + const def = variable.defs[0]; - if (def.type === 'Parameter') { - // either 'use gpu' or other parameter, either way invalid - context.report({ - messageId: 'parameterAssignment', - node, - data: { snippet: context.sourceCode.getText(node.left) }, - }); - } - } - }, - }; - }), -}); + // we check if it was defined outside of current function by checking ranges + if ( + def.node.range[1] < enclosingFn.range[0] || + enclosingFn.range[1] < def.node.range[0] + ) { + context.report({ + messageId: 'jsAssignment', + node, + data: { snippet: context.sourceCode.getText(leftNode) }, + }); + } + + if (def.type === 'Parameter') { + // either 'use gpu' or other parameter, either way invalid + context.report({ + messageId: 'parameterAssignment', + node, + data: { snippet: context.sourceCode.getText(leftNode) }, + }); + } + } +} From c674e359e889b84b927bb3d3827af878d537cfe8 Mon Sep 17 00:00:00 2001 From: Aleksander Katan Date: Mon, 2 Mar 2026 12:58:08 +0100 Subject: [PATCH 10/13] Handle parameters better --- .../src/rules/invalidAssignment.ts | 6 +- .../tests/invalidAssignment.test.ts | 89 +++++++++++++++++-- 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/src/rules/invalidAssignment.ts b/packages/eslint-plugin/src/rules/invalidAssignment.ts index 5e6be2c6ee..d26758d961 100644 --- a/packages/eslint-plugin/src/rules/invalidAssignment.ts +++ b/packages/eslint-plugin/src/rules/invalidAssignment.ts @@ -77,15 +77,17 @@ function checkAssignment( const def = variable.defs[0]; // we check if it was defined outside of current function by checking ranges + // NOTE: if the variable is an outer function parameter, then the enclosingFn range will be encompassed by node range if ( - def.node.range[1] < enclosingFn.range[0] || - enclosingFn.range[1] < def.node.range[0] + def.node.range[0] < enclosingFn.range[0] || + enclosingFn.range[1] < def.node.range[1] ) { context.report({ messageId: 'jsAssignment', node, data: { snippet: context.sourceCode.getText(leftNode) }, }); + return; } if (def.type === 'Parameter') { diff --git a/packages/eslint-plugin/tests/invalidAssignment.test.ts b/packages/eslint-plugin/tests/invalidAssignment.test.ts index f62431b029..d7b324b3af 100644 --- a/packages/eslint-plugin/tests/invalidAssignment.test.ts +++ b/packages/eslint-plugin/tests/invalidAssignment.test.ts @@ -72,11 +72,6 @@ describe('invalidAssignment', () => { { messageId: 'parameterAssignment', data: { snippet: 'b' } }, ], }, - { - code: - "const outer = (a) => { 'use gpu'; const inner = (b) => { 'use gpu'; b = 1; }; }", - errors: [{ messageId: 'parameterAssignment', data: { snippet: 'b' } }], - }, { code: "const fn = (a) => { 'use gpu'; a = 1; { let a; } }", errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], @@ -90,4 +85,88 @@ describe('invalidAssignment', () => { }, ], }); + + ruleTester.run('jsAssignment', invalidAssignment, { + valid: [ + 'let a; const fn = () => { a = 1 }', + 'const outer = (a) => { const fn = () => { a = 1 } }', + 'const vars = []; const fn = () => { vars[0] = 1 }', + "const buffer; const fn = () => { 'use gpu'; buffer.$ = 1 }", + "const outer = (buffer) => { const fn = () => { 'use gpu'; buffer.$ = 1 } }", + "const buffers = []; const fn = () => { 'use gpu'; buffers[0].$ = 1 }", + ], + invalid: [ + { + code: "let a; const fn = () => { 'use gpu'; a = 1 }", + errors: [{ messageId: 'jsAssignment', data: { snippet: 'a' } }], + }, + { + code: "const outer = (a) => { const fn = () => { 'use gpu'; a = 1 } }", + errors: [{ messageId: 'jsAssignment', data: { snippet: 'a' } }], + }, + { + code: "const vars = []; const fn = () => { 'use gpu'; vars[0] = 1 }", + errors: [{ messageId: 'jsAssignment', data: { snippet: 'vars[0]' } }], + }, + { + code: "let a; const fn = () => { 'use gpu'; a++; }", + errors: [{ messageId: 'jsAssignment', data: { snippet: 'a' } }], + }, + { + code: "let a; const fn = () => { 'use gpu'; a += 1; }", + errors: [{ messageId: 'jsAssignment', data: { snippet: 'a' } }], + }, + { + code: "const fn = () => { 'use gpu'; a += 1; }; let a;", + errors: [{ messageId: 'jsAssignment', data: { snippet: 'a' } }], + }, + { + code: "const a = {}; const fn = () => { 'use gpu'; a.prop = 1; }", + errors: [{ + messageId: 'jsAssignment', + data: { snippet: 'a.prop' }, + }], + }, + { + code: "const a = {}; const fn = () => { 'use gpu'; a['prop'] = 1; }", + errors: [{ + messageId: 'jsAssignment', + data: { snippet: "a['prop']" }, + }], + }, + { + code: "const a = []; const fn = () => { 'use gpu'; a[0] = 1; }", + errors: [{ + messageId: 'jsAssignment', + data: { snippet: 'a[0]' }, + }], + }, + { + code: + "const a = {}; const fn = () => { 'use gpu'; a.prop1.prop2 = 1; }", + errors: [{ + messageId: 'jsAssignment', + data: { snippet: 'a.prop1.prop2' }, + }], + }, + { + code: "let a; const fn = () => { 'use gpu'; if (true) { a = 1; } }", + errors: [{ messageId: 'jsAssignment', data: { snippet: 'a' } }], + }, + { + code: "let a, b; const fn = () => { 'use gpu'; a = 1; b = 2; }", + errors: [ + { messageId: 'jsAssignment', data: { snippet: 'a' } }, + { messageId: 'jsAssignment', data: { snippet: 'b' } }, + ], + }, + { + code: "const a = {}; const fn = () => { 'use gpu'; a.$prop = 1; }", + errors: [{ + messageId: 'jsAssignment', + data: { snippet: 'a.$prop' }, + }], + }, + ], + }); }); From ee168afdd6d0fa08880df41e4245b546ebd2259c Mon Sep 17 00:00:00 2001 From: Aleksander Katan Date: Mon, 2 Mar 2026 15:18:15 +0100 Subject: [PATCH 11/13] Cleanup & handle global assignments --- .../src/rules/invalidAssignment.ts | 65 +++++++++---------- .../tests/invalidAssignment.test.ts | 8 ++- 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/packages/eslint-plugin/src/rules/invalidAssignment.ts b/packages/eslint-plugin/src/rules/invalidAssignment.ts index d26758d961..947da9ebdd 100644 --- a/packages/eslint-plugin/src/rules/invalidAssignment.ts +++ b/packages/eslint-plugin/src/rules/invalidAssignment.ts @@ -2,7 +2,7 @@ import { ASTUtils, type TSESTree } from '@typescript-eslint/utils'; import { createRule } from '../ruleCreator.ts'; import { enhanceRule } from '../enhanceRule.ts'; import { directiveTracking } from '../enhancers/directiveTracking.ts'; -import { RuleContext } from '@typescript-eslint/utils/ts-eslint'; +import type { RuleContext } from '@typescript-eslint/utils/ts-eslint'; export const invalidAssignment = createRule({ name: 'invalid-assignment', @@ -27,18 +27,18 @@ export const invalidAssignment = createRule({ return { UpdateExpression(node) { const enclosingFn = directives.getEnclosingTypegpuFunction(); - checkAssignment(context, node, enclosingFn, node.argument); + validateAssignment(context, node, enclosingFn, node.argument); }, AssignmentExpression(node) { const enclosingFn = directives.getEnclosingTypegpuFunction(); - checkAssignment(context, node, enclosingFn, node.left); + validateAssignment(context, node, enclosingFn, node.left); }, }; }), }); -function checkAssignment( +function validateAssignment( context: Readonly>, node: TSESTree.Node, enclosingFn: TSESTree.Node | undefined, @@ -48,7 +48,7 @@ function checkAssignment( return; } - // look for the definition of the variable we assign to + // follow the member expression chain let assignee = leftNode; while (assignee.type === 'MemberExpression') { if ( @@ -64,39 +64,36 @@ function checkAssignment( return; } - // look for a scope that contains at least one + // look for a scope that defines the variable const variable = ASTUtils.findVariable( context.sourceCode.getScope(assignee), - assignee.name, + assignee, ); + // defs is an array because there may be multiple definitions with `var` + const def = variable?.defs[0]; - // TODO: handle variables with no defs (globalThis, Math etc.) - if (variable?.defs && variable.defs[0]) { - // def[0] points to the correct scope - // defs is an array because there may be multiple definitions with `var` - const def = variable.defs[0]; - - // we check if it was defined outside of current function by checking ranges - // NOTE: if the variable is an outer function parameter, then the enclosingFn range will be encompassed by node range - if ( - def.node.range[0] < enclosingFn.range[0] || - enclosingFn.range[1] < def.node.range[1] - ) { - context.report({ - messageId: 'jsAssignment', - node, - data: { snippet: context.sourceCode.getText(leftNode) }, - }); - return; - } + // check if variable is global or was defined outside of current function by checking ranges + // NOTE: if the variable is an outer function parameter, then the enclosingFn range will be encompassed by node range + if ( + !def || + def && ( + def.node.range[0] < enclosingFn.range[0] || + enclosingFn.range[1] < def.node.range[1] + ) + ) { + context.report({ + messageId: 'jsAssignment', + node, + data: { snippet: context.sourceCode.getText(leftNode) }, + }); + return; + } - if (def.type === 'Parameter') { - // either 'use gpu' or other parameter, either way invalid - context.report({ - messageId: 'parameterAssignment', - node, - data: { snippet: context.sourceCode.getText(leftNode) }, - }); - } + if (def.type === 'Parameter') { + context.report({ + messageId: 'parameterAssignment', + node, + data: { snippet: context.sourceCode.getText(leftNode) }, + }); } } diff --git a/packages/eslint-plugin/tests/invalidAssignment.test.ts b/packages/eslint-plugin/tests/invalidAssignment.test.ts index d7b324b3af..b37806bec4 100644 --- a/packages/eslint-plugin/tests/invalidAssignment.test.ts +++ b/packages/eslint-plugin/tests/invalidAssignment.test.ts @@ -2,7 +2,6 @@ import { describe } from 'vitest'; import { ruleTester } from './ruleTester.ts'; import { invalidAssignment } from '../src/rules/invalidAssignment.ts'; -// TODO: non-param assign describe('invalidAssignment', () => { ruleTester.run('parameterAssignment', invalidAssignment, { valid: [ @@ -167,6 +166,13 @@ describe('invalidAssignment', () => { data: { snippet: 'a.$prop' }, }], }, + { + code: "const fn = () => { 'use gpu'; globalThis.prop = 1 }", + errors: [{ + messageId: 'jsAssignment', + data: { snippet: 'globalThis.prop' }, + }], + }, ], }); }); From e100b60ae8f89a0c46283e6c9957528f38c40876 Mon Sep 17 00:00:00 2001 From: Aleksander Katan Date: Mon, 2 Mar 2026 15:30:15 +0100 Subject: [PATCH 12/13] Cleanup tests --- .../tests/invalidAssignment.test.ts | 68 +++++++++++-------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/packages/eslint-plugin/tests/invalidAssignment.test.ts b/packages/eslint-plugin/tests/invalidAssignment.test.ts index b37806bec4..abe4db1805 100644 --- a/packages/eslint-plugin/tests/invalidAssignment.test.ts +++ b/packages/eslint-plugin/tests/invalidAssignment.test.ts @@ -5,12 +5,17 @@ import { invalidAssignment } from '../src/rules/invalidAssignment.ts'; describe('invalidAssignment', () => { ruleTester.run('parameterAssignment', invalidAssignment, { valid: [ + // not inside 'use gpu' function 'const fn = (a) => { a = {}; }', 'const fn = (a) => { a.prop = 1; }', "const fn = (a) => { a['prop'] = 1; }", 'const fn = (a) => { a[0] = 1; }', - "const fn = (a) => { 'use gpu'; let x = 0; x = 1; }", + + // not using parameter + "const fn = (a) => { 'use gpu'; let b = 0; b = 1; }", "const fn = (a) => { 'use gpu'; { let a = 1; a = 2; } }", + + // correctly accessed "const fn = (a) => { 'use gpu'; a.$ = 1 }", "const fn = (a) => { 'use gpu'; a.$++; }", "const fn = (a) => { 'use gpu'; a.$ += 1; }", @@ -20,14 +25,6 @@ describe('invalidAssignment', () => { code: "const fn = (a) => { 'use gpu'; a = 1; }", errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], }, - { - code: "const fn = (a) => { 'use gpu'; a++; }", - errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], - }, - { - code: "const fn = (a) => { 'use gpu'; a += 1; }", - errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], - }, { code: "let a; const fn = (a) => { 'use gpu'; a = 1; }", errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], @@ -53,6 +50,14 @@ describe('invalidAssignment', () => { data: { snippet: 'a[0]' }, }], }, + { + code: "const fn = (a) => { 'use gpu'; a++; }", + errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], + }, + { + code: "const fn = (a) => { 'use gpu'; a += 1; }", + errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], + }, { code: "const fn = (a) => { 'use gpu'; a.prop1.prop2 = 1; }", errors: [{ @@ -64,6 +69,10 @@ describe('invalidAssignment', () => { code: "const fn = (a) => { 'use gpu'; if (true) { a = 1; } }", errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], }, + { + code: "const fn = (a) => { 'use gpu'; a = 1; { let a; } }", + errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], + }, { code: "const fn = (a, b) => { 'use gpu'; a = 1; b = 2; }", errors: [ @@ -71,10 +80,6 @@ describe('invalidAssignment', () => { { messageId: 'parameterAssignment', data: { snippet: 'b' } }, ], }, - { - code: "const fn = (a) => { 'use gpu'; a = 1; { let a; } }", - errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }], - }, { code: "const fn = (a) => { 'use gpu'; a.$prop = 1; }", errors: [{ @@ -87,10 +92,13 @@ describe('invalidAssignment', () => { ruleTester.run('jsAssignment', invalidAssignment, { valid: [ + // not inside 'use gpu' function 'let a; const fn = () => { a = 1 }', 'const outer = (a) => { const fn = () => { a = 1 } }', 'const vars = []; const fn = () => { vars[0] = 1 }', - "const buffer; const fn = () => { 'use gpu'; buffer.$ = 1 }", + + // correctly accessed + "const buffer = {}; const fn = () => { 'use gpu'; buffer.$ = 1 }", "const outer = (buffer) => { const fn = () => { 'use gpu'; buffer.$ = 1 } }", "const buffers = []; const fn = () => { 'use gpu'; buffers[0].$ = 1 }", ], @@ -100,23 +108,11 @@ describe('invalidAssignment', () => { errors: [{ messageId: 'jsAssignment', data: { snippet: 'a' } }], }, { - code: "const outer = (a) => { const fn = () => { 'use gpu'; a = 1 } }", - errors: [{ messageId: 'jsAssignment', data: { snippet: 'a' } }], - }, - { - code: "const vars = []; const fn = () => { 'use gpu'; vars[0] = 1 }", - errors: [{ messageId: 'jsAssignment', data: { snippet: 'vars[0]' } }], - }, - { - code: "let a; const fn = () => { 'use gpu'; a++; }", + code: "var a; const fn = () => { 'use gpu'; a = 1 }", errors: [{ messageId: 'jsAssignment', data: { snippet: 'a' } }], }, { - code: "let a; const fn = () => { 'use gpu'; a += 1; }", - errors: [{ messageId: 'jsAssignment', data: { snippet: 'a' } }], - }, - { - code: "const fn = () => { 'use gpu'; a += 1; }; let a;", + code: "const outer = (a) => { const fn = () => { 'use gpu'; a = 1 } }", errors: [{ messageId: 'jsAssignment', data: { snippet: 'a' } }], }, { @@ -140,6 +136,22 @@ describe('invalidAssignment', () => { data: { snippet: 'a[0]' }, }], }, + { + code: "const vars = []; const fn = () => { 'use gpu'; vars[0] = 1 }", + errors: [{ messageId: 'jsAssignment', data: { snippet: 'vars[0]' } }], + }, + { + code: "const fn = () => { 'use gpu'; a += 1; }; let a;", + errors: [{ messageId: 'jsAssignment', data: { snippet: 'a' } }], + }, + { + code: "let a; const fn = () => { 'use gpu'; a++; }", + errors: [{ messageId: 'jsAssignment', data: { snippet: 'a' } }], + }, + { + code: "let a; const fn = () => { 'use gpu'; a += 1; }", + errors: [{ messageId: 'jsAssignment', data: { snippet: 'a' } }], + }, { code: "const a = {}; const fn = () => { 'use gpu'; a.prop1.prop2 = 1; }", From eaec41607a98812dafc6ca4d4d6f042642967766 Mon Sep 17 00:00:00 2001 From: Aleksander Katan Date: Mon, 2 Mar 2026 15:46:08 +0100 Subject: [PATCH 13/13] Adjust tests in typegpu repo --- packages/typegpu/tests/ref.test.ts | 4 ++-- packages/typegpu/tests/tgsl/argumentOrigin.test.ts | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/typegpu/tests/ref.test.ts b/packages/typegpu/tests/ref.test.ts index 1470936a17..36a9db3acd 100644 --- a/packages/typegpu/tests/ref.test.ts +++ b/packages/typegpu/tests/ref.test.ts @@ -114,7 +114,7 @@ describe('d.ref', () => { const clearPosition = (entity: d.ref) => { 'use gpu'; - entity.pos = d.vec3f(); + entity.$.pos = d.vec3f(); }; const main = () => { @@ -149,7 +149,7 @@ describe('d.ref', () => { it('allows updating a vector component from another function', () => { const clearX = (pos: d.ref) => { 'use gpu'; - pos.x = 0; + pos.$.x = 0; }; const main = () => { diff --git a/packages/typegpu/tests/tgsl/argumentOrigin.test.ts b/packages/typegpu/tests/tgsl/argumentOrigin.test.ts index ed2ad8494a..e56a9d3972 100644 --- a/packages/typegpu/tests/tgsl/argumentOrigin.test.ts +++ b/packages/typegpu/tests/tgsl/argumentOrigin.test.ts @@ -6,6 +6,7 @@ describe('function argument origin tracking', () => { it('should fail on mutation of primitive arguments', () => { const foo = (a: number) => { 'use gpu'; + // oxlint-disable-next-line typegpu/invalid-assignment -- this is a test a += 1; }; @@ -28,6 +29,7 @@ describe('function argument origin tracking', () => { const foo = ({ a }: { a: number }) => { 'use gpu'; + // oxlint-disable-next-line typegpu/invalid-assignment -- this is a test a += 1; }; @@ -48,6 +50,7 @@ describe('function argument origin tracking', () => { it('should fail on mutation of non-primitive arguments', () => { const foo = (a: d.v3f) => { 'use gpu'; + // oxlint-disable-next-line typegpu/invalid-assignment -- this is a test a.x += 1; };