Skip to content

Commit f07c8e0

Browse files
feat: More lint rules (#2153)
1 parent 7b9ee18 commit f07c8e0

6 files changed

Lines changed: 181 additions & 0 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
11
import type { TSESLint } from '@typescript-eslint/utils';
22
import { integerDivision } from './rules/integerDivision.ts';
33
import { unwrappedPojos } from './rules/unwrappedPojos.ts';
4+
import { math } from './rules/math.ts';
5+
import { uninitializedVariable } from './rules/uninitializedVariable.ts';
46

57
export const rules = {
68
'integer-division': integerDivision,
79
'unwrapped-pojo': unwrappedPojos,
10+
'uninitialized-variable': uninitializedVariable,
11+
math: math,
812
} as const;
913

1014
type Rules = Record<`typegpu/${keyof typeof rules}`, TSESLint.FlatConfig.RuleEntry>;
1115

1216
export const recommendedRules: Rules = {
1317
'typegpu/integer-division': 'warn',
1418
'typegpu/unwrapped-pojo': 'warn',
19+
'typegpu/uninitialized-variable': 'warn',
20+
'typegpu/math': 'warn',
1521
};
1622

1723
export const allRules: Rules = {
1824
'typegpu/integer-division': 'error',
1925
'typegpu/unwrapped-pojo': 'error',
26+
'typegpu/uninitialized-variable': 'error',
27+
'typegpu/math': 'error',
2028
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { createRule } from '../ruleCreator.ts';
2+
import { enhanceRule } from '../enhanceRule.ts';
3+
import { directiveTracking } from '../enhancers/directiveTracking.ts';
4+
import type { RuleContext } from '@typescript-eslint/utils/ts-eslint';
5+
import { ASTUtils, type TSESTree } from '@typescript-eslint/utils';
6+
7+
export const math = createRule({
8+
name: 'math',
9+
meta: {
10+
type: 'suggestion',
11+
docs: {
12+
description: `Disallow usage of JavaScript 'Math' methods inside 'use gpu' functions; use 'std' instead.`,
13+
},
14+
messages: {
15+
math: "Using Math methods, such as '{{snippet}}', is not advised, and may not work as expected. Use 'std' instead.",
16+
},
17+
schema: [],
18+
},
19+
defaultOptions: [],
20+
21+
create: enhanceRule({ directives: directiveTracking }, (context, state) => {
22+
const { directives } = state;
23+
24+
return {
25+
CallExpression(node) {
26+
if (!directives.insideUseGpu()) {
27+
return;
28+
}
29+
30+
if (
31+
node.callee.type === 'MemberExpression' &&
32+
node.callee.object.type === 'Identifier' &&
33+
node.callee.object.name === 'Math' &&
34+
isGlobalIdentifier(context, node.callee.object)
35+
) {
36+
context.report({
37+
node,
38+
messageId: 'math',
39+
data: { snippet: context.sourceCode.getText(node) },
40+
});
41+
}
42+
},
43+
};
44+
}),
45+
});
46+
47+
function isGlobalIdentifier(
48+
context: Readonly<RuleContext<string, unknown[]>>,
49+
node: TSESTree.Identifier,
50+
) {
51+
const variable = ASTUtils.findVariable(context.sourceCode.getScope(node), node);
52+
if (!variable) {
53+
throw new Error(`Couldn't find variable ${node.name}.`);
54+
}
55+
return variable.defs.length === 0;
56+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { enhanceRule } from '../enhanceRule.ts';
2+
import { directiveTracking } from '../enhancers/directiveTracking.ts';
3+
import { createRule } from '../ruleCreator.ts';
4+
5+
export const uninitializedVariable = createRule({
6+
name: 'uninitialized-variable',
7+
meta: {
8+
type: 'problem',
9+
docs: {
10+
description: `Always assign an initial value when declaring a variable inside TypeGPU functions.`,
11+
},
12+
messages: {
13+
uninitializedVariable: "'{{snippet}}' should have an initial value.",
14+
},
15+
schema: [],
16+
},
17+
defaultOptions: [],
18+
19+
create: enhanceRule({ directives: directiveTracking }, (context, state) => {
20+
const { directives } = state;
21+
22+
return {
23+
VariableDeclarator(node) {
24+
if (!directives.insideUseGpu()) {
25+
return;
26+
}
27+
if (node.parent?.parent?.type === 'ForOfStatement') {
28+
// one exception where we allow uninitialized variable
29+
return;
30+
}
31+
if (node.init === null) {
32+
context.report({
33+
node,
34+
messageId: 'uninitializedVariable',
35+
data: { snippet: context.sourceCode.getText(node) },
36+
});
37+
}
38+
},
39+
};
40+
}),
41+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { describe } from 'vitest';
2+
import { ruleTester } from './ruleTester.ts';
3+
import { math } from '../src/rules/math.ts';
4+
5+
describe('math', () => {
6+
ruleTester.run('math', math, {
7+
valid: [
8+
'const result = Math.sin(1);',
9+
'const t = std.sin(Math.PI)',
10+
"const fn = () => { 'use gpu'; const vec = std.sin(Math.PI); }",
11+
"const Math = { sin: std.sin }; const fn = () => { 'use gpu'; const vec = Math.sin(0); }",
12+
"import Math from 'utils'; const fn = () => { 'use gpu'; const vec = Math.sin(0); }",
13+
],
14+
invalid: [
15+
{
16+
code: "const fn = () => { 'use gpu'; const vec = Math.sin(0); }",
17+
errors: [
18+
{
19+
messageId: 'math',
20+
data: { snippet: 'Math.sin(0)' },
21+
},
22+
],
23+
},
24+
],
25+
});
26+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { describe } from 'vitest';
2+
import { ruleTester } from './ruleTester.ts';
3+
import { uninitializedVariable } from '../src/rules/uninitializedVariable.ts';
4+
5+
describe('uninitializedVariable', () => {
6+
ruleTester.run('uninitializedVariable', uninitializedVariable, {
7+
valid: [
8+
'let a;',
9+
'let a, b;',
10+
"const fn = () => { 'use gpu'; const vec = d.vec3f(); }",
11+
"const fn = () => { 'use gpu'; let vec = d.vec3f(); }",
12+
`const fn = () => { 'use gpu';
13+
let a = 0;
14+
for (const foo of tgpu.unroll([1, 2, 3])) {
15+
a += foo;
16+
}
17+
}`,
18+
],
19+
invalid: [
20+
{
21+
code: "const fn = () => { 'use gpu'; let vec; }",
22+
errors: [
23+
{
24+
messageId: 'uninitializedVariable',
25+
data: { snippet: 'vec' },
26+
},
27+
],
28+
},
29+
{
30+
code: "const fn = () => { 'use gpu'; let a = 1, b, c = d.vec3f(), d; }",
31+
errors: [
32+
{
33+
messageId: 'uninitializedVariable',
34+
data: { snippet: 'b' },
35+
},
36+
{
37+
messageId: 'uninitializedVariable',
38+
data: { snippet: 'd' },
39+
},
40+
],
41+
},
42+
],
43+
});
44+
});

packages/typegpu/tests/jsMath.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ describe('Math', () => {
1919
const myFn = () => {
2020
'use gpu';
2121
const a = 0.5;
22+
// oxlint-disable-next-line typegpu/math
2223
const b = Math.sin(a);
2324
};
2425

@@ -33,6 +34,7 @@ describe('Math', () => {
3334
it('precomputes Math.sin when applicable', () => {
3435
const myFn = () => {
3536
'use gpu';
37+
// oxlint-disable-next-line typegpu/math
3638
const a = Math.sin(0.5);
3739
};
3840

@@ -47,6 +49,7 @@ describe('Math', () => {
4749
const myFn = () => {
4850
'use gpu';
4951
const a = d.u32();
52+
// oxlint-disable-next-line typegpu/math
5053
const b = Math.sin(a);
5154
};
5255

@@ -62,6 +65,7 @@ describe('Math', () => {
6265
const myFn = () => {
6366
'use gpu';
6467
const a = d.u32();
68+
// oxlint-disable-next-line typegpu/math
6569
const b = Math.min(a, 1, 2, 3);
6670
};
6771

@@ -76,6 +80,7 @@ describe('Math', () => {
7680
it('throws a readable error when unsupported Math feature is used', () => {
7781
const myFn = () => {
7882
'use gpu';
83+
// oxlint-disable-next-line typegpu/math
7984
const a = Math.log1p(1);
8085
};
8186

@@ -90,6 +95,7 @@ describe('Math', () => {
9095
it('correctly applies Math.fround', () => {
9196
const myFn = () => {
9297
'use gpu';
98+
// oxlint-disable-next-line typegpu/math
9399
const a = Math.fround(16777217);
94100
};
95101

0 commit comments

Comments
 (0)