From c9f0366e49492c5cd92f0d91a00a2a0d545fa1e4 Mon Sep 17 00:00:00 2001
From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com>
Date: Thu, 2 Apr 2026 17:32:58 +0200
Subject: [PATCH 01/12] Duplicate eslint plugin
---
.../.eslint-doc-generatorrc.mjs | 9 +
packages/eslint-plugin-internal/README.md | 51 +++++
.../docs/rules/no-integer-division.md | 34 +++
.../docs/rules/no-invalid-assignment.md | 54 +++++
.../docs/rules/no-math.md | 31 +++
.../docs/rules/no-uninitialized-variables.md | 31 +++
.../docs/rules/no-unwrapped-objects.md | 38 ++++
packages/eslint-plugin-internal/package.json | 65 ++++++
.../eslint-plugin-internal/src/configs.ts | 32 +++
.../eslint-plugin-internal/src/enhanceRule.ts | 80 +++++++
.../src/enhancers/directiveTracking.ts | 83 +++++++
packages/eslint-plugin-internal/src/index.ts | 33 +++
.../eslint-plugin-internal/src/nodeHelpers.ts | 20 ++
.../eslint-plugin-internal/src/ruleCreator.ts | 6 +
.../src/rules/noIntegerDivision.ts | 61 +++++
.../src/rules/noInvalidAssignment.ts | 90 ++++++++
.../src/rules/noMath.ts | 57 +++++
.../src/rules/noUninitializedVariables.ts | 41 ++++
.../src/rules/noUnwrappedObjects.ts | 49 ++++
.../tests/ruleNames.test.ts | 8 +
.../tests/rules/noIntegerDivision.test.ts | 45 ++++
.../tests/rules/noInvalidAssignment.test.ts | 210 ++++++++++++++++++
.../tests/rules/noMath.test.ts | 26 +++
.../rules/noUninitializedVariables.test.ts | 44 ++++
.../tests/rules/noUnwrappedObjects.test.ts | 79 +++++++
.../tests/utils/ruleTester.ts | 10 +
packages/eslint-plugin-internal/tsconfig.json | 8 +
.../eslint-plugin-internal/tsdown.config.ts | 9 +
.../eslint-plugin-internal/vitest.config.ts | 8 +
29 files changed, 1312 insertions(+)
create mode 100644 packages/eslint-plugin-internal/.eslint-doc-generatorrc.mjs
create mode 100644 packages/eslint-plugin-internal/README.md
create mode 100644 packages/eslint-plugin-internal/docs/rules/no-integer-division.md
create mode 100644 packages/eslint-plugin-internal/docs/rules/no-invalid-assignment.md
create mode 100644 packages/eslint-plugin-internal/docs/rules/no-math.md
create mode 100644 packages/eslint-plugin-internal/docs/rules/no-uninitialized-variables.md
create mode 100644 packages/eslint-plugin-internal/docs/rules/no-unwrapped-objects.md
create mode 100644 packages/eslint-plugin-internal/package.json
create mode 100644 packages/eslint-plugin-internal/src/configs.ts
create mode 100644 packages/eslint-plugin-internal/src/enhanceRule.ts
create mode 100644 packages/eslint-plugin-internal/src/enhancers/directiveTracking.ts
create mode 100644 packages/eslint-plugin-internal/src/index.ts
create mode 100644 packages/eslint-plugin-internal/src/nodeHelpers.ts
create mode 100644 packages/eslint-plugin-internal/src/ruleCreator.ts
create mode 100644 packages/eslint-plugin-internal/src/rules/noIntegerDivision.ts
create mode 100644 packages/eslint-plugin-internal/src/rules/noInvalidAssignment.ts
create mode 100644 packages/eslint-plugin-internal/src/rules/noMath.ts
create mode 100644 packages/eslint-plugin-internal/src/rules/noUninitializedVariables.ts
create mode 100644 packages/eslint-plugin-internal/src/rules/noUnwrappedObjects.ts
create mode 100644 packages/eslint-plugin-internal/tests/ruleNames.test.ts
create mode 100644 packages/eslint-plugin-internal/tests/rules/noIntegerDivision.test.ts
create mode 100644 packages/eslint-plugin-internal/tests/rules/noInvalidAssignment.test.ts
create mode 100644 packages/eslint-plugin-internal/tests/rules/noMath.test.ts
create mode 100644 packages/eslint-plugin-internal/tests/rules/noUninitializedVariables.test.ts
create mode 100644 packages/eslint-plugin-internal/tests/rules/noUnwrappedObjects.test.ts
create mode 100644 packages/eslint-plugin-internal/tests/utils/ruleTester.ts
create mode 100644 packages/eslint-plugin-internal/tsconfig.json
create mode 100644 packages/eslint-plugin-internal/tsdown.config.ts
create mode 100644 packages/eslint-plugin-internal/vitest.config.ts
diff --git a/packages/eslint-plugin-internal/.eslint-doc-generatorrc.mjs b/packages/eslint-plugin-internal/.eslint-doc-generatorrc.mjs
new file mode 100644
index 0000000000..0604c2e099
--- /dev/null
+++ b/packages/eslint-plugin-internal/.eslint-doc-generatorrc.mjs
@@ -0,0 +1,9 @@
+const config = {
+ ignoreConfig: ['all'],
+ configEmoji: [['recommended', '⭐']],
+ postprocess: (content) => {
+ return content.replaceAll('💼', '🚨').replaceAll('🚫', '💤');
+ },
+};
+
+export default config;
diff --git a/packages/eslint-plugin-internal/README.md b/packages/eslint-plugin-internal/README.md
new file mode 100644
index 0000000000..fae1227e74
--- /dev/null
+++ b/packages/eslint-plugin-internal/README.md
@@ -0,0 +1,51 @@
+
+
+# eslint-plugin-typegpu
+
+TypeGPU specific linting rules for ESLint.
+
+[Docs](https://docs.swmansion.com/TypeGPU/tooling/eslint-plugin-typegpu/) -- [GitHub](https://github.com/software-mansion/TypeGPU/tree/main/packages/eslint-plugin) -- [npm](https://www.npmjs.com/package/eslint-plugin-typegpu)
+
+
+
+## Installation
+
+`npm add -D eslint-plugin-typegpu`
+
+After installing, the plugin needs to be configured.
+
+## Configuration
+
+Configuration depends on the linter used.
+
+In eslint, either define the used rules manually, or use one of the configs provided by the plugin.
+
+```ts
+import { defineConfig } from "eslint/config";
+import typegpu from "eslint-plugin-typegpu";
+
+export default defineConfig([
+// other configs
+ typegpu.configs.recommended,
+]);
+```
+
+`eslint-plugin-typegpu` provides two configs: `all` (enabled on all rules) and `recommended`.
+
+## List of supported rules
+
+
+
+🚨 Configurations enabled in.\
+⚠️ Configurations set to warn in.\
+⭐ Set in the `recommended` configuration.
+
+| Name | Description | 🚨 | ⚠️ |
+| :--------------------------------------------------------------------- | :------------------------------------------------------------------------------------------ | :- | :- |
+| [no-integer-division](docs/rules/no-integer-division.md) | Disallow division incorporating numbers wrapped in 'u32' and 'i32' | | ⭐ |
+| [no-invalid-assignment](docs/rules/no-invalid-assignment.md) | Disallow assignments that will generate invalid WGSL | ⭐ | |
+| [no-math](docs/rules/no-math.md) | Disallow usage of JavaScript 'Math' methods inside 'use gpu' functions | | ⭐ |
+| [no-uninitialized-variables](docs/rules/no-uninitialized-variables.md) | Disallow variable declarations without initializers inside 'use gpu' functions | ⭐ | |
+| [no-unwrapped-objects](docs/rules/no-unwrapped-objects.md) | Disallow unwrapped Plain Old JavaScript Objects inside 'use gpu' functions (except returns) | ⭐ | |
+
+
diff --git a/packages/eslint-plugin-internal/docs/rules/no-integer-division.md b/packages/eslint-plugin-internal/docs/rules/no-integer-division.md
new file mode 100644
index 0000000000..4c934c6282
--- /dev/null
+++ b/packages/eslint-plugin-internal/docs/rules/no-integer-division.md
@@ -0,0 +1,34 @@
+# typegpu/no-integer-division
+
+📝 Disallow division incorporating numbers wrapped in 'u32' and 'i32'.
+
+⚠️ This rule _warns_ in the ⭐ `recommended` config.
+
+
+
+## Rule details
+
+Examples of **incorrect** code for this rule:
+
+```ts
+const a = d.u32(1) / d.u32(2);
+```
+```ts
+const a = 1 / d.u32(2);
+```
+```ts
+const a = 1 / d.i32(2);
+```
+
+Examples of **correct** code for this rule:
+
+```ts
+const a = 1 / 2;
+```
+```ts
+const a = d.u32(d.u32(1) / d.u32(2));
+```
+
+Note that this rule is not type aware.
+Extracting the dividend and the divisor to variables will silence the rule,
+but it will not make the code behave differently.
\ No newline at end of file
diff --git a/packages/eslint-plugin-internal/docs/rules/no-invalid-assignment.md b/packages/eslint-plugin-internal/docs/rules/no-invalid-assignment.md
new file mode 100644
index 0000000000..5fb48326f4
--- /dev/null
+++ b/packages/eslint-plugin-internal/docs/rules/no-invalid-assignment.md
@@ -0,0 +1,54 @@
+# typegpu/no-invalid-assignment
+
+📝 Disallow assignments that will generate invalid WGSL.
+
+🚨 This rule is enabled in the ⭐ `recommended` config.
+
+
+
+## Rule details
+
+Examples of **incorrect** code for this rule:
+
+```ts
+const fn = (a) => {
+ 'use gpu';
+ a = 1;
+}
+```
+```ts
+const fn = (a) => {
+ 'use gpu';
+ a.prop++;
+}
+```
+```ts
+let a;
+const fn = () => {
+ 'use gpu';
+ a = 1;
+}
+```
+
+Examples of **correct** code for this rule:
+
+```ts
+const fn = () => {
+ 'use gpu';
+ const ref = d.ref(0);
+ other(ref);
+};
+
+const other = (ref: d.ref) => {
+ 'use gpu';
+ ref.$ = 1;
+};
+```
+```ts
+const privateVar = tgpu.privateVar(d.u32);
+const fn = () => {
+ 'use gpu';
+ privateVar.$ = 1;
+}
+```
+
diff --git a/packages/eslint-plugin-internal/docs/rules/no-math.md b/packages/eslint-plugin-internal/docs/rules/no-math.md
new file mode 100644
index 0000000000..c136e5d04c
--- /dev/null
+++ b/packages/eslint-plugin-internal/docs/rules/no-math.md
@@ -0,0 +1,31 @@
+# typegpu/no-math
+
+📝 Disallow usage of JavaScript 'Math' methods inside 'use gpu' functions.
+
+⚠️ This rule _warns_ in the ⭐ `recommended` config.
+
+
+
+## Rule details
+
+Examples of **incorrect** code for this rule:
+
+```ts
+const fn = () => {
+ 'use gpu';
+ const vec = Math.sin(0);
+}
+```
+
+Examples of **correct** code for this rule:
+
+```ts
+const fn = () => {
+ 'use gpu';
+ const a = std.sin(Math.PI);
+}
+```
+```ts
+// outside 'use gpu'
+const a = Math.sin(1);
+```
\ No newline at end of file
diff --git a/packages/eslint-plugin-internal/docs/rules/no-uninitialized-variables.md b/packages/eslint-plugin-internal/docs/rules/no-uninitialized-variables.md
new file mode 100644
index 0000000000..f272ab93b9
--- /dev/null
+++ b/packages/eslint-plugin-internal/docs/rules/no-uninitialized-variables.md
@@ -0,0 +1,31 @@
+# typegpu/no-uninitialized-variables
+
+📝 Disallow variable declarations without initializers inside 'use gpu' functions.
+
+🚨 This rule is enabled in the ⭐ `recommended` config.
+
+
+
+## Rule details
+
+Examples of **incorrect** code for this rule:
+
+```ts
+const fn = () => {
+ 'use gpu';
+ let a;
+}
+```
+
+Examples of **correct** code for this rule:
+
+```ts
+const fn = () => {
+ 'use gpu';
+ let vec = d.vec3f();
+}
+```
+```ts
+// outside 'use gpu'
+let a;
+```
diff --git a/packages/eslint-plugin-internal/docs/rules/no-unwrapped-objects.md b/packages/eslint-plugin-internal/docs/rules/no-unwrapped-objects.md
new file mode 100644
index 0000000000..bcdeb7e20c
--- /dev/null
+++ b/packages/eslint-plugin-internal/docs/rules/no-unwrapped-objects.md
@@ -0,0 +1,38 @@
+# typegpu/no-unwrapped-objects
+
+📝 Disallow unwrapped Plain Old JavaScript Objects inside 'use gpu' functions (except returns).
+
+🚨 This rule is enabled in the ⭐ `recommended` config.
+
+
+
+## Rule details
+
+Examples of **incorrect** code for this rule:
+
+```ts
+const fn = () => {
+ 'use gpu';
+ const unwrapped = { a: 1 };
+}
+```
+
+Examples of **correct** code for this rule:
+
+```ts
+const pojo = { a: 1 };
+```
+```ts
+const fn = () => {
+ 'use gpu';
+ return { a: 1 };
+}
+```
+```ts
+const Schema = d.struct({ a: d.u32 });
+
+const fn = () => {
+ 'use gpu';
+ const wrapped = Schema({ a: 1 });
+}
+```
\ No newline at end of file
diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json
new file mode 100644
index 0000000000..003192a77f
--- /dev/null
+++ b/packages/eslint-plugin-internal/package.json
@@ -0,0 +1,65 @@
+{
+ "name": "eslint-plugin-typegpu",
+ "version": "0.10.0-alpha.2",
+ "description": "TypeGPU specific linting rules for ESLint",
+ "keywords": [
+ "eslint",
+ "eslint-plugin",
+ "eslintplugin",
+ "typegpu",
+ "use gpu"
+ ],
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/software-mansion/TypeGPU.git#main",
+ "directory": "packages/eslint-plugin"
+ },
+ "files": [
+ "dist"
+ ],
+ "type": "module",
+ "sideEffects": false,
+ "main": "./src/index.ts",
+ "exports": {
+ ".": {
+ "types": "./src/index.ts",
+ "import": "./src/index.ts"
+ }
+ },
+ "publishConfig": {
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.mts",
+ "import": "./dist/index.mjs"
+ }
+ },
+ "main": "./dist/index.mjs",
+ "types": "./dist/index.d.mts"
+ },
+ "scripts": {
+ "build": "tsdown",
+ "test:types": "pnpm tsc --p ./tsconfig.json --noEmit",
+ "docs:init": "eslint-doc-generator --init-rule-docs",
+ "docs:check": "eslint-doc-generator --check",
+ "docs:update": "eslint-doc-generator",
+ "test": "vitest",
+ "prepublishOnly": "pnpm run docs:check && pnpm run test:types && vitest run && pnpm run build"
+ },
+ "dependencies": {
+ "@typescript-eslint/utils": "^8.57.2"
+ },
+ "devDependencies": {
+ "@types/node": "catalog:types",
+ "@typescript-eslint/rule-tester": "^8.57.2",
+ "eslint": "^9.39.2",
+ "eslint-doc-generator": "^3.3.2",
+ "tsdown": "^0.20.3",
+ "typescript": "^5.9.3",
+ "vitest": "^4.0.17"
+ },
+ "peerDependencies": {
+ "eslint": "^9.0.0"
+ },
+ "packageManager": "pnpm@10.15.1"
+}
diff --git a/packages/eslint-plugin-internal/src/configs.ts b/packages/eslint-plugin-internal/src/configs.ts
new file mode 100644
index 0000000000..df130b1dcd
--- /dev/null
+++ b/packages/eslint-plugin-internal/src/configs.ts
@@ -0,0 +1,32 @@
+import type { TSESLint } from '@typescript-eslint/utils';
+import { noIntegerDivision } from './rules/noIntegerDivision.ts';
+import { noUnwrappedObjects } from './rules/noUnwrappedObjects.ts';
+import { noMath } from './rules/noMath.ts';
+import { noUninitializedVariables } from './rules/noUninitializedVariables.ts';
+import { noInvalidAssignment } from './rules/noInvalidAssignment.ts';
+
+export const rules = {
+ 'no-integer-division': noIntegerDivision,
+ 'no-unwrapped-objects': noUnwrappedObjects,
+ 'no-uninitialized-variables': noUninitializedVariables,
+ 'no-math': noMath,
+ 'no-invalid-assignment': noInvalidAssignment,
+} as const;
+
+type Rules = Record<`typegpu/${keyof typeof rules}`, TSESLint.FlatConfig.RuleEntry>;
+
+export const recommendedRules: Rules = {
+ 'typegpu/no-integer-division': 'warn',
+ 'typegpu/no-unwrapped-objects': 'error',
+ 'typegpu/no-uninitialized-variables': 'error',
+ 'typegpu/no-math': 'warn',
+ 'typegpu/no-invalid-assignment': 'error',
+};
+
+export const allRules: Rules = {
+ 'typegpu/no-integer-division': 'error',
+ 'typegpu/no-unwrapped-objects': 'error',
+ 'typegpu/no-uninitialized-variables': 'error',
+ 'typegpu/no-math': 'error',
+ 'typegpu/no-invalid-assignment': 'error',
+};
diff --git a/packages/eslint-plugin-internal/src/enhanceRule.ts b/packages/eslint-plugin-internal/src/enhanceRule.ts
new file mode 100644
index 0000000000..5d0a366b68
--- /dev/null
+++ b/packages/eslint-plugin-internal/src/enhanceRule.ts
@@ -0,0 +1,80 @@
+import type { RuleContext, RuleListener } from '@typescript-eslint/utils/ts-eslint';
+
+export type RuleEnhancer = (context: RuleContext) => {
+ visitors: RuleListener;
+ state: TState;
+};
+
+type State>> = {
+ [K in keyof TMap]: TMap[K] extends RuleEnhancer ? S : never;
+};
+
+/**
+ * Allows enhancing rule code with additional context provided by RuleEnhancers (reusable node visitors collecting data).
+ * @param enhancers a record of RuleEnhancers
+ * @param rule a visitor with an additional `state` argument that allows access to the enhancers' data
+ * @returns a resulting `(context: Context) => RuleListener` function
+ *
+ * @example
+ * // inside of `createRule`
+ * create: enhanceRule({ metadata: metadataTrackingEnhancer }, (context, state) => {
+ * const { metadata } = state;
+ *
+ * return {
+ * ObjectExpression(node) {
+ * if (metadata.shouldReport()) {
+ * context.report({ node, messageId: 'error' });
+ * }
+ * },
+ * };
+ */
+export function enhanceRule<
+ TMap extends Record>,
+ Context extends RuleContext,
+>(enhancers: TMap, rule: (context: Context, state: State) => RuleListener) {
+ return (context: Context) => {
+ const enhancerVisitors: RuleListener[] = [];
+ const combinedState: Record = {};
+
+ for (const [key, enhancer] of Object.entries(enhancers)) {
+ const initializedEnhancer = enhancer(context);
+ enhancerVisitors.push(initializedEnhancer.visitors);
+ combinedState[key] = initializedEnhancer.state;
+ }
+
+ const initializedRule = rule(context, combinedState as State);
+
+ return mergeVisitors([...enhancerVisitors, initializedRule]);
+ };
+}
+
+/**
+ * Merges all passed visitors into one visitor.
+ * Retains visitor order:
+ * - on node enter, visitors are called in `visitorsList` order,
+ * - on node exit, visitors are called in reversed order.
+ */
+function mergeVisitors(visitors: RuleListener[]): RuleListener {
+ const merged: RuleListener = {};
+
+ const allKeys = new Set(visitors.flatMap((v) => Object.keys(v)));
+
+ for (const key of allKeys) {
+ const listeners = visitors.map((v) => v[key]).filter((fn) => fn !== undefined);
+
+ if (listeners.length === 0) {
+ continue;
+ }
+
+ // Reverse order if node is an exit node
+ if (key.endsWith(':exit')) {
+ listeners.reverse();
+ }
+
+ merged[key] = (...args: unknown[]) => {
+ listeners.forEach((fn) => (fn as (...args: unknown[]) => void)(...args));
+ };
+ }
+
+ return merged;
+}
diff --git a/packages/eslint-plugin-internal/src/enhancers/directiveTracking.ts b/packages/eslint-plugin-internal/src/enhancers/directiveTracking.ts
new file mode 100644
index 0000000000..b490760afb
--- /dev/null
+++ b/packages/eslint-plugin-internal/src/enhancers/directiveTracking.ts
@@ -0,0 +1,83 @@
+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 = {
+ getEnclosingTypegpuFunction: () => FunctionNode | undefined;
+};
+
+/**
+ * A RuleEnhancer that tracks whether the current node is inside a 'use gpu' function.
+ *
+ * @privateRemarks
+ * Should the need arise, the API could be updated to expose:
+ * - a list of directives of the current function,
+ * - directives of other visited functions,
+ * - top level directives.
+ */
+export const directiveTracking: RuleEnhancer = () => {
+ const stack: { node: FunctionNode; directives: string[] }[] = [];
+
+ const visitors: RuleListener = {
+ FunctionDeclaration(node) {
+ stack.push({ node, directives: getDirectives(node) });
+ },
+ FunctionExpression(node) {
+ stack.push({ node, directives: getDirectives(node) });
+ },
+ ArrowFunctionExpression(node) {
+ stack.push({ node, directives: getDirectives(node) });
+ },
+
+ 'FunctionDeclaration:exit'() {
+ stack.pop();
+ },
+ 'FunctionExpression:exit'() {
+ stack.pop();
+ },
+ 'ArrowFunctionExpression:exit'() {
+ stack.pop();
+ },
+ };
+
+ return {
+ visitors,
+ state: {
+ getEnclosingTypegpuFunction: () => {
+ const current = stack.at(-1);
+ if (current && current.directives.includes('use gpu')) {
+ return current.node;
+ }
+ return undefined;
+ },
+ },
+ };
+};
+
+function getDirectives(
+ node:
+ | TSESTree.FunctionDeclaration
+ | TSESTree.FunctionExpression
+ | TSESTree.ArrowFunctionExpression,
+): string[] {
+ const body = node.body;
+ if (body.type !== 'BlockStatement') {
+ return [];
+ }
+
+ const directives: string[] = [];
+ for (const statement of body.body) {
+ if (statement.type === 'ExpressionStatement' && statement.directive) {
+ directives.push(statement.directive);
+ } else {
+ break;
+ }
+ }
+
+ return directives;
+}
diff --git a/packages/eslint-plugin-internal/src/index.ts b/packages/eslint-plugin-internal/src/index.ts
new file mode 100644
index 0000000000..08763811d3
--- /dev/null
+++ b/packages/eslint-plugin-internal/src/index.ts
@@ -0,0 +1,33 @@
+import pkg from '../package.json' with { type: 'json' };
+import type { TSESLint } from '@typescript-eslint/utils';
+import { allRules, recommendedRules, rules } from './configs.ts';
+
+const pluginBase: TSESLint.FlatConfig.Plugin = {
+ meta: {
+ name: pkg.name,
+ version: pkg.version,
+ },
+ rules,
+};
+
+const recommended: TSESLint.FlatConfig.Config = {
+ name: 'typegpu/recommended',
+ plugins: { typegpu: pluginBase },
+ rules: recommendedRules,
+};
+
+const all: TSESLint.FlatConfig.Config = {
+ name: 'typegpu/all',
+ plugins: { typegpu: pluginBase },
+ rules: allRules,
+};
+
+const plugin: TSESLint.FlatConfig.Plugin = {
+ ...pluginBase,
+ configs: {
+ recommended,
+ all,
+ },
+};
+
+export default plugin;
diff --git a/packages/eslint-plugin-internal/src/nodeHelpers.ts b/packages/eslint-plugin-internal/src/nodeHelpers.ts
new file mode 100644
index 0000000000..4122427d17
--- /dev/null
+++ b/packages/eslint-plugin-internal/src/nodeHelpers.ts
@@ -0,0 +1,20 @@
+import { TSESTree } from '@typescript-eslint/utils';
+
+const transparentNodes = [
+ 'TSAsExpression',
+ 'TSSatisfiesExpression',
+ 'TSTypeAssertion',
+ 'TSNonNullExpression',
+];
+
+export function isTransparent(node: TSESTree.Node): boolean {
+ return transparentNodes.includes(node.type);
+}
+
+export function getNonTransparentParent(node: TSESTree.Node) {
+ let parent = node.parent;
+ while (parent && isTransparent(parent)) {
+ parent = parent.parent;
+ }
+ return parent;
+}
diff --git a/packages/eslint-plugin-internal/src/ruleCreator.ts b/packages/eslint-plugin-internal/src/ruleCreator.ts
new file mode 100644
index 0000000000..6d07477ffc
--- /dev/null
+++ b/packages/eslint-plugin-internal/src/ruleCreator.ts
@@ -0,0 +1,6 @@
+import { ESLintUtils } from '@typescript-eslint/utils';
+
+export const createRule = ESLintUtils.RuleCreator(
+ // TODO: docs for lint rules
+ () => `https://docs.swmansion.com/TypeGPU/getting-started/`,
+);
diff --git a/packages/eslint-plugin-internal/src/rules/noIntegerDivision.ts b/packages/eslint-plugin-internal/src/rules/noIntegerDivision.ts
new file mode 100644
index 0000000000..99664f29c7
--- /dev/null
+++ b/packages/eslint-plugin-internal/src/rules/noIntegerDivision.ts
@@ -0,0 +1,61 @@
+import type { TSESTree } from '@typescript-eslint/utils';
+import { createRule } from '../ruleCreator.ts';
+
+// TODO: detect `std.div(d.u32(1), d.u32(2))`
+export const noIntegerDivision = createRule({
+ name: 'no-integer-division',
+ meta: {
+ type: 'suggestion',
+ docs: { description: `Disallow division incorporating numbers wrapped in 'u32' and 'i32'` },
+ messages: {
+ suspiciousDivision:
+ "'{{snippet}}' might result in floating point values. To perform integer division, wrap the result in 'd.u32' or 'd.i32' instead",
+ },
+ schema: [],
+ },
+ defaultOptions: [],
+
+ create(context) {
+ return {
+ BinaryExpression(node) {
+ if (node.operator !== '/') {
+ return;
+ }
+
+ if (node.parent?.type === 'CallExpression' && isIntCast(node.parent)) {
+ return;
+ }
+
+ if (isIntCast(node.left) || isIntCast(node.right)) {
+ context.report({
+ node,
+ messageId: 'suspiciousDivision',
+ data: { snippet: context.sourceCode.getText(node) },
+ });
+ }
+ },
+ };
+ },
+});
+
+/**
+ * Checks if a node is a call expression to an integer cast function (i32 or u32).
+ *
+ * @example
+ * // for simplicity, using code snippets instead of ASTs
+ * isIntCasts('d.u32()'); // true
+ * isIntCasts('i32()'); // true
+ * isIntCasts('f32()'); // false
+ */
+function isIntCast(node: TSESTree.Expression): boolean {
+ if (node.type !== 'CallExpression') {
+ return false;
+ }
+
+ let callee: TSESTree.Node = node.callee;
+ while (callee.type === 'MemberExpression') {
+ callee = callee.property;
+ }
+
+ return callee.type === 'Identifier' && ['i32', 'u32'].includes(callee.name);
+}
diff --git a/packages/eslint-plugin-internal/src/rules/noInvalidAssignment.ts b/packages/eslint-plugin-internal/src/rules/noInvalidAssignment.ts
new file mode 100644
index 0000000000..80d4cbf278
--- /dev/null
+++ b/packages/eslint-plugin-internal/src/rules/noInvalidAssignment.ts
@@ -0,0 +1,90 @@
+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 type { RuleContext } from '@typescript-eslint/utils/ts-eslint';
+
+export const noInvalidAssignment = createRule({
+ name: 'no-invalid-assignment',
+ meta: {
+ type: 'problem',
+ docs: {
+ description: `Disallow assignments that will generate invalid WGSL`,
+ },
+ 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: [],
+ },
+ defaultOptions: [],
+
+ create: enhanceRule({ directives: directiveTracking }, (context, state) => {
+ const { directives } = state;
+
+ return {
+ UpdateExpression(node) {
+ const enclosingFn = directives.getEnclosingTypegpuFunction();
+ validateAssignment(context, node, enclosingFn, node.argument);
+ },
+
+ AssignmentExpression(node) {
+ const enclosingFn = directives.getEnclosingTypegpuFunction();
+ validateAssignment(context, node, enclosingFn, node.left);
+ },
+ };
+ }),
+});
+
+function validateAssignment(
+ context: Readonly>,
+ node: TSESTree.Node,
+ enclosingFn: TSESTree.Node | undefined,
+ leftNode: TSESTree.Node,
+) {
+ if (!enclosingFn) {
+ return;
+ }
+
+ // follow the member expression chain
+ 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;
+ }
+
+ // look for a scope that defines the variable
+ const variable = ASTUtils.findVariable(context.sourceCode.getScope(assignee), assignee);
+ // defs is an array because there may be multiple definitions with `var`
+ const def = variable?.defs[0];
+
+ // 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') {
+ context.report({
+ messageId: 'parameterAssignment',
+ node,
+ data: { snippet: context.sourceCode.getText(leftNode) },
+ });
+ }
+}
diff --git a/packages/eslint-plugin-internal/src/rules/noMath.ts b/packages/eslint-plugin-internal/src/rules/noMath.ts
new file mode 100644
index 0000000000..3a70cb3dda
--- /dev/null
+++ b/packages/eslint-plugin-internal/src/rules/noMath.ts
@@ -0,0 +1,57 @@
+import { createRule } from '../ruleCreator.ts';
+import { enhanceRule } from '../enhanceRule.ts';
+import { directiveTracking } from '../enhancers/directiveTracking.ts';
+import type { RuleContext } from '@typescript-eslint/utils/ts-eslint';
+import { ASTUtils, type TSESTree } from '@typescript-eslint/utils';
+
+export const noMath = createRule({
+ name: 'no-math',
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: `Disallow usage of JavaScript 'Math' methods inside 'use gpu' functions`,
+ },
+ messages: {
+ unexpected:
+ "Using Math methods, such as '{{snippet}}', may not work as expected. Use 'std' instead",
+ },
+ schema: [],
+ },
+ defaultOptions: [],
+
+ create: enhanceRule({ directives: directiveTracking }, (context, state) => {
+ const { directives } = state;
+
+ return {
+ CallExpression(node) {
+ if (!directives.getEnclosingTypegpuFunction()) {
+ return;
+ }
+
+ if (
+ node.callee.type === 'MemberExpression' &&
+ node.callee.object.type === 'Identifier' &&
+ node.callee.object.name === 'Math' &&
+ isGlobalIdentifier(context, node.callee.object)
+ ) {
+ context.report({
+ node,
+ messageId: 'unexpected',
+ data: { snippet: context.sourceCode.getText(node) },
+ });
+ }
+ },
+ };
+ }),
+});
+
+function isGlobalIdentifier(
+ context: Readonly>,
+ node: TSESTree.Identifier,
+) {
+ const variable = ASTUtils.findVariable(context.sourceCode.getScope(node), node);
+ if (!variable) {
+ throw new Error(`Couldn't find variable ${node.name}.`);
+ }
+ return variable.defs.length === 0;
+}
diff --git a/packages/eslint-plugin-internal/src/rules/noUninitializedVariables.ts b/packages/eslint-plugin-internal/src/rules/noUninitializedVariables.ts
new file mode 100644
index 0000000000..c889ca1b2d
--- /dev/null
+++ b/packages/eslint-plugin-internal/src/rules/noUninitializedVariables.ts
@@ -0,0 +1,41 @@
+import { enhanceRule } from '../enhanceRule.ts';
+import { directiveTracking } from '../enhancers/directiveTracking.ts';
+import { createRule } from '../ruleCreator.ts';
+
+export const noUninitializedVariables = createRule({
+ name: 'no-uninitialized-variables',
+ meta: {
+ type: 'problem',
+ docs: {
+ description: `Disallow variable declarations without initializers inside 'use gpu' functions`,
+ },
+ messages: {
+ uninitializedVariable: "'{{snippet}}' must have an initial value",
+ },
+ schema: [],
+ },
+ defaultOptions: [],
+
+ create: enhanceRule({ directives: directiveTracking }, (context, state) => {
+ const { directives } = state;
+
+ return {
+ VariableDeclarator(node) {
+ if (!directives.getEnclosingTypegpuFunction()) {
+ return;
+ }
+ if (node.parent?.parent?.type === 'ForOfStatement') {
+ // one exception where we allow uninitialized variable
+ return;
+ }
+ if (node.init === null) {
+ context.report({
+ node,
+ messageId: 'uninitializedVariable',
+ data: { snippet: context.sourceCode.getText(node) },
+ });
+ }
+ },
+ };
+ }),
+});
diff --git a/packages/eslint-plugin-internal/src/rules/noUnwrappedObjects.ts b/packages/eslint-plugin-internal/src/rules/noUnwrappedObjects.ts
new file mode 100644
index 0000000000..99f6b4d180
--- /dev/null
+++ b/packages/eslint-plugin-internal/src/rules/noUnwrappedObjects.ts
@@ -0,0 +1,49 @@
+import { enhanceRule } from '../enhanceRule.ts';
+import { directiveTracking } from '../enhancers/directiveTracking.ts';
+import { getNonTransparentParent } from '../nodeHelpers.ts';
+import { createRule } from '../ruleCreator.ts';
+
+export const noUnwrappedObjects = createRule({
+ name: 'no-unwrapped-objects',
+ meta: {
+ type: 'problem',
+ docs: {
+ description: `Disallow unwrapped Plain Old JavaScript Objects inside 'use gpu' functions (except returns)`,
+ },
+ messages: {
+ unexpected: '{{snippet}} must be wrapped in a schema call',
+ },
+ schema: [],
+ },
+ defaultOptions: [],
+
+ create: enhanceRule({ directives: directiveTracking }, (context, state) => {
+ const { directives } = state;
+
+ return {
+ ObjectExpression(node) {
+ if (!directives.getEnclosingTypegpuFunction()) {
+ return;
+ }
+ let parent = getNonTransparentParent(node);
+ if (parent?.type === 'Property') {
+ // a part of a bigger struct
+ return;
+ }
+ if (parent?.type === 'CallExpression') {
+ // wrapped in a schema call
+ return;
+ }
+ if (parent?.type === 'ReturnStatement') {
+ // likely inferred (shelled fn or shell-less entry) so we cannot report
+ return;
+ }
+ context.report({
+ node,
+ messageId: 'unexpected',
+ data: { snippet: context.sourceCode.getText(node) },
+ });
+ },
+ };
+ }),
+});
diff --git a/packages/eslint-plugin-internal/tests/ruleNames.test.ts b/packages/eslint-plugin-internal/tests/ruleNames.test.ts
new file mode 100644
index 0000000000..d0c9f968cc
--- /dev/null
+++ b/packages/eslint-plugin-internal/tests/ruleNames.test.ts
@@ -0,0 +1,8 @@
+import { expect, it } from 'vitest';
+import { rules } from '../src/configs.ts';
+
+it('uses the same names for rules and exports', () => {
+ for (const key in rules) {
+ expect(rules[key as keyof typeof rules].name).toBe(key);
+ }
+});
diff --git a/packages/eslint-plugin-internal/tests/rules/noIntegerDivision.test.ts b/packages/eslint-plugin-internal/tests/rules/noIntegerDivision.test.ts
new file mode 100644
index 0000000000..509b8e1f99
--- /dev/null
+++ b/packages/eslint-plugin-internal/tests/rules/noIntegerDivision.test.ts
@@ -0,0 +1,45 @@
+import { describe } from 'vitest';
+import { noIntegerDivision } from '../../src/rules/noIntegerDivision.ts';
+import { ruleTester } from '../utils/ruleTester.ts';
+
+describe('noIntegerDivision', () => {
+ ruleTester.run('noIntegerDivision', noIntegerDivision, {
+ valid: ['1 / 2', 'd.u32(d.u32(1) / d.u32(2))'],
+ invalid: [
+ {
+ code: 'd.u32(1) / 2',
+ errors: [{ messageId: 'suspiciousDivision', data: { snippet: 'd.u32(1) / 2' } }],
+ },
+ {
+ code: '1 / d.u32(2)',
+ errors: [{ messageId: 'suspiciousDivision', data: { snippet: '1 / d.u32(2)' } }],
+ },
+ {
+ code: 'd.u32(1) / d.u32(2)',
+ errors: [{ messageId: 'suspiciousDivision', data: { snippet: 'd.u32(1) / d.u32(2)' } }],
+ },
+ {
+ code: 'd.i32(1) / d.i32(2)',
+ errors: [{ messageId: 'suspiciousDivision', data: { snippet: 'd.i32(1) / d.i32(2)' } }],
+ },
+ {
+ code: 'd.u32(1) / d.i32(2)',
+ errors: [{ messageId: 'suspiciousDivision', data: { snippet: 'd.u32(1) / d.i32(2)' } }],
+ },
+ {
+ code: 'u32(1) / u32(2)',
+ errors: [{ messageId: 'suspiciousDivision', data: { snippet: 'u32(1) / u32(2)' } }],
+ },
+ {
+ code: 'd.u32(1) / d.u32(2) / d.u32(3)',
+ errors: [
+ {
+ messageId: 'suspiciousDivision',
+ data: { snippet: 'd.u32(1) / d.u32(2) / d.u32(3)' },
+ },
+ { messageId: 'suspiciousDivision', data: { snippet: 'd.u32(1) / d.u32(2)' } },
+ ],
+ },
+ ],
+ });
+});
diff --git a/packages/eslint-plugin-internal/tests/rules/noInvalidAssignment.test.ts b/packages/eslint-plugin-internal/tests/rules/noInvalidAssignment.test.ts
new file mode 100644
index 0000000000..b06d16a33a
--- /dev/null
+++ b/packages/eslint-plugin-internal/tests/rules/noInvalidAssignment.test.ts
@@ -0,0 +1,210 @@
+import { describe } from 'vitest';
+import { ruleTester } from '../utils/ruleTester.ts';
+import { noInvalidAssignment } from '../../src/rules/noInvalidAssignment.ts';
+
+describe('noInvalidAssignment', () => {
+ ruleTester.run('noInvalidAssignment', noInvalidAssignment, {
+ 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; }',
+
+ // 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; }",
+ ],
+ 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.prop = 1; }",
+ errors: [
+ {
+ messageId: 'parameterAssignment',
+ data: { snippet: 'a.prop' },
+ },
+ ],
+ },
+ {
+ 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++; }",
+ 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: "const fn = (a) => { 'use gpu'; a += 1; }",
+ errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }],
+ },
+ {
+ code: "const fn = (a) => { 'use gpu'; a.prop1.prop2 = 1; }",
+ errors: [
+ {
+ messageId: 'parameterAssignment',
+ data: { snippet: 'a.prop1.prop2' },
+ },
+ ],
+ },
+ {
+ 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: [
+ { messageId: 'parameterAssignment', data: { snippet: 'a' } },
+ { messageId: 'parameterAssignment', data: { snippet: 'b' } },
+ ],
+ },
+ {
+ code: "const fn = (a) => { 'use gpu'; a.$prop = 1; }",
+ errors: [
+ {
+ messageId: 'parameterAssignment',
+ data: { snippet: 'a.$prop' },
+ },
+ ],
+ },
+ ],
+ });
+
+ ruleTester.run('invalidAssignment', noInvalidAssignment, {
+ 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 }',
+
+ // 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 }",
+ ],
+ invalid: [
+ {
+ code: "let a; const fn = () => { 'use gpu'; a = 1 }",
+ errors: [{ messageId: 'jsAssignment', data: { snippet: 'a' } }],
+ },
+ {
+ code: "var 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 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 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; }",
+ 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' },
+ },
+ ],
+ },
+ {
+ code: "const fn = () => { 'use gpu'; globalThis.prop = 1 }",
+ errors: [
+ {
+ messageId: 'jsAssignment',
+ data: { snippet: 'globalThis.prop' },
+ },
+ ],
+ },
+ ],
+ });
+});
diff --git a/packages/eslint-plugin-internal/tests/rules/noMath.test.ts b/packages/eslint-plugin-internal/tests/rules/noMath.test.ts
new file mode 100644
index 0000000000..3ddd2708f8
--- /dev/null
+++ b/packages/eslint-plugin-internal/tests/rules/noMath.test.ts
@@ -0,0 +1,26 @@
+import { describe } from 'vitest';
+import { ruleTester } from '../utils/ruleTester.ts';
+import { noMath } from '../../src/rules/noMath.ts';
+
+describe('noMath', () => {
+ ruleTester.run('noMath', noMath, {
+ valid: [
+ 'const result = Math.sin(1);',
+ 'const t = std.sin(Math.PI)',
+ "const fn = () => { 'use gpu'; const vec = std.sin(Math.PI); }",
+ "const Math = { sin: std.sin }; const fn = () => { 'use gpu'; const vec = Math.sin(0); }",
+ "import Math from 'utils'; const fn = () => { 'use gpu'; const vec = Math.sin(0); }",
+ ],
+ invalid: [
+ {
+ code: "const fn = () => { 'use gpu'; const vec = Math.sin(0); }",
+ errors: [
+ {
+ messageId: 'unexpected',
+ data: { snippet: 'Math.sin(0)' },
+ },
+ ],
+ },
+ ],
+ });
+});
diff --git a/packages/eslint-plugin-internal/tests/rules/noUninitializedVariables.test.ts b/packages/eslint-plugin-internal/tests/rules/noUninitializedVariables.test.ts
new file mode 100644
index 0000000000..96dc6ca5c3
--- /dev/null
+++ b/packages/eslint-plugin-internal/tests/rules/noUninitializedVariables.test.ts
@@ -0,0 +1,44 @@
+import { describe } from 'vitest';
+import { ruleTester } from '../utils/ruleTester.ts';
+import { noUninitializedVariables } from '../../src/rules/noUninitializedVariables.ts';
+
+describe('noUninitializedVariables', () => {
+ ruleTester.run('noUninitializedVariables', noUninitializedVariables, {
+ valid: [
+ 'let a;',
+ 'let a, b;',
+ "const fn = () => { 'use gpu'; const vec = d.vec3f(); }",
+ "const fn = () => { 'use gpu'; let vec = d.vec3f(); }",
+ `const fn = () => { 'use gpu';
+ let a = 0;
+ for (const foo of tgpu.unroll([1, 2, 3])) {
+ a += foo;
+ }
+ }`,
+ ],
+ invalid: [
+ {
+ code: "const fn = () => { 'use gpu'; let vec; }",
+ errors: [
+ {
+ messageId: 'uninitializedVariable',
+ data: { snippet: 'vec' },
+ },
+ ],
+ },
+ {
+ code: "const fn = () => { 'use gpu'; let a = 1, b, c = d.vec3f(), d; }",
+ errors: [
+ {
+ messageId: 'uninitializedVariable',
+ data: { snippet: 'b' },
+ },
+ {
+ messageId: 'uninitializedVariable',
+ data: { snippet: 'd' },
+ },
+ ],
+ },
+ ],
+ });
+});
diff --git a/packages/eslint-plugin-internal/tests/rules/noUnwrappedObjects.test.ts b/packages/eslint-plugin-internal/tests/rules/noUnwrappedObjects.test.ts
new file mode 100644
index 0000000000..1ca1e919a5
--- /dev/null
+++ b/packages/eslint-plugin-internal/tests/rules/noUnwrappedObjects.test.ts
@@ -0,0 +1,79 @@
+import { describe } from 'vitest';
+import { ruleTester } from '../utils/ruleTester.ts';
+import { noUnwrappedObjects } from '../../src/rules/noUnwrappedObjects.ts';
+
+describe('noUnwrappedObjects', () => {
+ ruleTester.run('noUnwrappedObjects', noUnwrappedObjects, {
+ valid: [
+ // correctly wrapped
+ "function func() { 'use gpu'; const wrapped = Schema({ a: 1 }); }",
+ "const func = function() { 'use gpu'; const wrapped = Schema({ a: 1 }); }",
+ "() => { 'use gpu'; const wrapped = Schema({ a: 1 }); }",
+
+ // not inside 'use gpu' function
+ 'const pojo = { a: 1 };',
+ 'function func() { const unwrapped = { a: 1 }; }',
+ 'const func = function () { const unwrapped = { a: 1 }; }',
+ '() => { const unwrapped = { a: 1 }; }',
+ 'function func() { return { a: 1 }; }',
+ 'const func = function () { return { a: 1 }; }',
+ '() => { return { a: 1 }; }',
+
+ // return from 'use gpu' function
+ "function func() { 'use gpu'; return { a: 1 }; }",
+ "const func = function() { 'use gpu'; return { a: 1 }; }",
+ "() => { 'use gpu'; return { a: 1 }; }",
+ "() => { 'use gpu'; return { a: { b: 1 } }; }",
+ "() => { 'use gpu'; return { a: 1 } as typeof Struct; }",
+ "() => { 'use gpu'; return { a: 1 } satisfies Struct; }",
+ "() => { 'use gpu'; return ({ a: 1 }); }",
+ ],
+ invalid: [
+ {
+ code: "function func() { 'use gpu'; const unwrapped = { a: 1 }; }",
+ errors: [
+ {
+ messageId: 'unexpected',
+ data: { snippet: '{ a: 1 }' },
+ },
+ ],
+ },
+ {
+ code: "const func = function() { 'use gpu'; const unwrapped = { a: 1 }; }",
+ errors: [
+ {
+ messageId: 'unexpected',
+ data: { snippet: '{ a: 1 }' },
+ },
+ ],
+ },
+ {
+ code: "() => { 'use gpu'; const unwrapped = { a: 1 }; }",
+ errors: [
+ {
+ messageId: 'unexpected',
+ data: { snippet: '{ a: 1 }' },
+ },
+ ],
+ },
+ {
+ code: "function func() { 'unknown directive'; 'use gpu'; const unwrapped = { a: 1 }; }",
+ errors: [
+ {
+ messageId: 'unexpected',
+ data: { snippet: '{ a: 1 }' },
+ },
+ ],
+ },
+ {
+ code: "() => { 'use gpu'; const unwrapped = { a: { b: 1 } }; }",
+ errors: [
+ {
+ messageId: 'unexpected',
+ data: { snippet: '{ a: { b: 1 } }' },
+ },
+ ],
+ },
+ ],
+ });
+});
diff --git a/packages/eslint-plugin-internal/tests/utils/ruleTester.ts b/packages/eslint-plugin-internal/tests/utils/ruleTester.ts
new file mode 100644
index 0000000000..3efee657d3
--- /dev/null
+++ b/packages/eslint-plugin-internal/tests/utils/ruleTester.ts
@@ -0,0 +1,10 @@
+import { RuleTester } from '@typescript-eslint/rule-tester';
+import { afterAll, describe, it } from 'vitest';
+
+// RuleTester relies on global hooks for tests.
+// Vitest doesn't make the hooks available globally, so we need to bind them.
+RuleTester.describe = describe;
+RuleTester.it = it;
+RuleTester.afterAll = afterAll;
+
+export const ruleTester = new RuleTester();
diff --git a/packages/eslint-plugin-internal/tsconfig.json b/packages/eslint-plugin-internal/tsconfig.json
new file mode 100644
index 0000000000..3a544d3dd8
--- /dev/null
+++ b/packages/eslint-plugin-internal/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "types": ["node"]
+ },
+ "include": ["src/**/*", "tests/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/eslint-plugin-internal/tsdown.config.ts b/packages/eslint-plugin-internal/tsdown.config.ts
new file mode 100644
index 0000000000..3ec1155cc3
--- /dev/null
+++ b/packages/eslint-plugin-internal/tsdown.config.ts
@@ -0,0 +1,9 @@
+import { defineConfig } from 'tsdown';
+
+export default defineConfig({
+ entry: ['src/index.ts'],
+ format: ['esm'],
+ dts: true,
+ clean: true,
+ sourcemap: true,
+});
diff --git a/packages/eslint-plugin-internal/vitest.config.ts b/packages/eslint-plugin-internal/vitest.config.ts
new file mode 100644
index 0000000000..b0e95a5a89
--- /dev/null
+++ b/packages/eslint-plugin-internal/vitest.config.ts
@@ -0,0 +1,8 @@
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ test: {
+ include: ['tests/**/*.test.ts'],
+ environment: 'node',
+ },
+});
From 3c52c108ae21aec28d8902c4c7edcd7676c13957 Mon Sep 17 00:00:00 2001
From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com>
Date: Fri, 3 Apr 2026 10:57:53 +0200
Subject: [PATCH 02/12] Strip the plugin of unused code, enable plugin in
config
---
oxlint.config.ts | 3 +-
package.json | 1 +
.../docs/rules/no-integer-division.md | 34 ---
.../docs/rules/no-invalid-assignment.md | 54 -----
.../docs/rules/no-math.md | 31 ---
.../docs/rules/no-uninitialized-variables.md | 31 ---
.../docs/rules/no-unwrapped-objects.md | 38 ----
packages/eslint-plugin-internal/package.json | 37 +--
.../eslint-plugin-internal/src/configs.ts | 32 ---
.../eslint-plugin-internal/src/enhanceRule.ts | 80 -------
.../src/enhancers/directiveTracking.ts | 83 -------
packages/eslint-plugin-internal/src/index.ts | 28 +--
.../eslint-plugin-internal/src/nodeHelpers.ts | 20 --
.../eslint-plugin-internal/src/ruleCreator.ts | 1 -
.../src/rules/noIntegerDivision.ts | 61 -----
.../src/rules/noInvalidAssignment.ts | 90 --------
.../src/rules/noMath.ts | 42 +---
.../src/rules/noUninitializedVariables.ts | 41 ----
.../src/rules/noUnwrappedObjects.ts | 49 ----
.../tests/ruleNames.test.ts | 8 -
.../tests/rules/noIntegerDivision.test.ts | 45 ----
.../tests/rules/noInvalidAssignment.test.ts | 210 ------------------
.../tests/rules/noMath.test.ts | 26 ---
.../rules/noUninitializedVariables.test.ts | 44 ----
.../tests/rules/noUnwrappedObjects.test.ts | 79 -------
pnpm-lock.yaml | 31 +++
26 files changed, 52 insertions(+), 1147 deletions(-)
delete mode 100644 packages/eslint-plugin-internal/docs/rules/no-integer-division.md
delete mode 100644 packages/eslint-plugin-internal/docs/rules/no-invalid-assignment.md
delete mode 100644 packages/eslint-plugin-internal/docs/rules/no-math.md
delete mode 100644 packages/eslint-plugin-internal/docs/rules/no-uninitialized-variables.md
delete mode 100644 packages/eslint-plugin-internal/docs/rules/no-unwrapped-objects.md
delete mode 100644 packages/eslint-plugin-internal/src/configs.ts
delete mode 100644 packages/eslint-plugin-internal/src/enhanceRule.ts
delete mode 100644 packages/eslint-plugin-internal/src/enhancers/directiveTracking.ts
delete mode 100644 packages/eslint-plugin-internal/src/nodeHelpers.ts
delete mode 100644 packages/eslint-plugin-internal/src/rules/noIntegerDivision.ts
delete mode 100644 packages/eslint-plugin-internal/src/rules/noInvalidAssignment.ts
delete mode 100644 packages/eslint-plugin-internal/src/rules/noUninitializedVariables.ts
delete mode 100644 packages/eslint-plugin-internal/src/rules/noUnwrappedObjects.ts
delete mode 100644 packages/eslint-plugin-internal/tests/ruleNames.test.ts
delete mode 100644 packages/eslint-plugin-internal/tests/rules/noIntegerDivision.test.ts
delete mode 100644 packages/eslint-plugin-internal/tests/rules/noInvalidAssignment.test.ts
delete mode 100644 packages/eslint-plugin-internal/tests/rules/noMath.test.ts
delete mode 100644 packages/eslint-plugin-internal/tests/rules/noUninitializedVariables.test.ts
delete mode 100644 packages/eslint-plugin-internal/tests/rules/noUnwrappedObjects.test.ts
diff --git a/oxlint.config.ts b/oxlint.config.ts
index 37983778e0..07521e72e9 100644
--- a/oxlint.config.ts
+++ b/oxlint.config.ts
@@ -7,7 +7,7 @@ const typegpuRules = typegpuPreset && 'rules' in typegpuPreset ? typegpuPreset.r
export default defineConfig({
plugins: ['eslint', 'typescript', 'import', 'unicorn', 'oxc'],
- jsPlugins: ['eslint-plugin-typegpu', 'eslint-plugin-eslint-plugin'],
+ jsPlugins: ['eslint-plugin-typegpu', 'eslint-plugin-eslint-plugin', 'eslint-plugin-internal'],
categories: {
correctness: 'warn',
suspicious: 'warn',
@@ -24,6 +24,7 @@ export default defineConfig({
'eslint-plugin-import/no-named-as-default': 'off',
'eslint-plugin-import/no-named-as-default-member': 'off',
'eslint-plugin-import/extensions': ['error', 'always', { ignorePackages: true }],
+ 'eslint-plugin-internal/no-math': 'error',
},
ignorePatterns: ['**/*.astro', '**/*.mjs'],
overrides: [
diff --git a/package.json b/package.json
index d9c199c59d..7711ca82a6 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
"@webgpu/types": "catalog:types",
"dpdm": "^3.14.0",
"eslint-plugin-eslint-plugin": "^7.3.2",
+ "eslint-plugin-internal": "workspace:*",
"eslint-plugin-typegpu": "workspace:*",
"jiti": "catalog:build",
"oxfmt": "^0.35.0",
diff --git a/packages/eslint-plugin-internal/docs/rules/no-integer-division.md b/packages/eslint-plugin-internal/docs/rules/no-integer-division.md
deleted file mode 100644
index 4c934c6282..0000000000
--- a/packages/eslint-plugin-internal/docs/rules/no-integer-division.md
+++ /dev/null
@@ -1,34 +0,0 @@
-# typegpu/no-integer-division
-
-📝 Disallow division incorporating numbers wrapped in 'u32' and 'i32'.
-
-⚠️ This rule _warns_ in the ⭐ `recommended` config.
-
-
-
-## Rule details
-
-Examples of **incorrect** code for this rule:
-
-```ts
-const a = d.u32(1) / d.u32(2);
-```
-```ts
-const a = 1 / d.u32(2);
-```
-```ts
-const a = 1 / d.i32(2);
-```
-
-Examples of **correct** code for this rule:
-
-```ts
-const a = 1 / 2;
-```
-```ts
-const a = d.u32(d.u32(1) / d.u32(2));
-```
-
-Note that this rule is not type aware.
-Extracting the dividend and the divisor to variables will silence the rule,
-but it will not make the code behave differently.
\ No newline at end of file
diff --git a/packages/eslint-plugin-internal/docs/rules/no-invalid-assignment.md b/packages/eslint-plugin-internal/docs/rules/no-invalid-assignment.md
deleted file mode 100644
index 5fb48326f4..0000000000
--- a/packages/eslint-plugin-internal/docs/rules/no-invalid-assignment.md
+++ /dev/null
@@ -1,54 +0,0 @@
-# typegpu/no-invalid-assignment
-
-📝 Disallow assignments that will generate invalid WGSL.
-
-🚨 This rule is enabled in the ⭐ `recommended` config.
-
-
-
-## Rule details
-
-Examples of **incorrect** code for this rule:
-
-```ts
-const fn = (a) => {
- 'use gpu';
- a = 1;
-}
-```
-```ts
-const fn = (a) => {
- 'use gpu';
- a.prop++;
-}
-```
-```ts
-let a;
-const fn = () => {
- 'use gpu';
- a = 1;
-}
-```
-
-Examples of **correct** code for this rule:
-
-```ts
-const fn = () => {
- 'use gpu';
- const ref = d.ref(0);
- other(ref);
-};
-
-const other = (ref: d.ref) => {
- 'use gpu';
- ref.$ = 1;
-};
-```
-```ts
-const privateVar = tgpu.privateVar(d.u32);
-const fn = () => {
- 'use gpu';
- privateVar.$ = 1;
-}
-```
-
diff --git a/packages/eslint-plugin-internal/docs/rules/no-math.md b/packages/eslint-plugin-internal/docs/rules/no-math.md
deleted file mode 100644
index c136e5d04c..0000000000
--- a/packages/eslint-plugin-internal/docs/rules/no-math.md
+++ /dev/null
@@ -1,31 +0,0 @@
-# typegpu/no-math
-
-📝 Disallow usage of JavaScript 'Math' methods inside 'use gpu' functions.
-
-⚠️ This rule _warns_ in the ⭐ `recommended` config.
-
-
-
-## Rule details
-
-Examples of **incorrect** code for this rule:
-
-```ts
-const fn = () => {
- 'use gpu';
- const vec = Math.sin(0);
-}
-```
-
-Examples of **correct** code for this rule:
-
-```ts
-const fn = () => {
- 'use gpu';
- const a = std.sin(Math.PI);
-}
-```
-```ts
-// outside 'use gpu'
-const a = Math.sin(1);
-```
\ No newline at end of file
diff --git a/packages/eslint-plugin-internal/docs/rules/no-uninitialized-variables.md b/packages/eslint-plugin-internal/docs/rules/no-uninitialized-variables.md
deleted file mode 100644
index f272ab93b9..0000000000
--- a/packages/eslint-plugin-internal/docs/rules/no-uninitialized-variables.md
+++ /dev/null
@@ -1,31 +0,0 @@
-# typegpu/no-uninitialized-variables
-
-📝 Disallow variable declarations without initializers inside 'use gpu' functions.
-
-🚨 This rule is enabled in the ⭐ `recommended` config.
-
-
-
-## Rule details
-
-Examples of **incorrect** code for this rule:
-
-```ts
-const fn = () => {
- 'use gpu';
- let a;
-}
-```
-
-Examples of **correct** code for this rule:
-
-```ts
-const fn = () => {
- 'use gpu';
- let vec = d.vec3f();
-}
-```
-```ts
-// outside 'use gpu'
-let a;
-```
diff --git a/packages/eslint-plugin-internal/docs/rules/no-unwrapped-objects.md b/packages/eslint-plugin-internal/docs/rules/no-unwrapped-objects.md
deleted file mode 100644
index bcdeb7e20c..0000000000
--- a/packages/eslint-plugin-internal/docs/rules/no-unwrapped-objects.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# typegpu/no-unwrapped-objects
-
-📝 Disallow unwrapped Plain Old JavaScript Objects inside 'use gpu' functions (except returns).
-
-🚨 This rule is enabled in the ⭐ `recommended` config.
-
-
-
-## Rule details
-
-Examples of **incorrect** code for this rule:
-
-```ts
-const fn = () => {
- 'use gpu';
- const unwrapped = { a: 1 };
-}
-```
-
-Examples of **correct** code for this rule:
-
-```ts
-const pojo = { a: 1 };
-```
-```ts
-const fn = () => {
- 'use gpu';
- return { a: 1 };
-}
-```
-```ts
-const Schema = d.struct({ a: d.u32 });
-
-const fn = () => {
- 'use gpu';
- const wrapped = Schema({ a: 1 });
-}
-```
\ No newline at end of file
diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json
index 003192a77f..0ed80ab20d 100644
--- a/packages/eslint-plugin-internal/package.json
+++ b/packages/eslint-plugin-internal/package.json
@@ -1,23 +1,14 @@
{
- "name": "eslint-plugin-typegpu",
- "version": "0.10.0-alpha.2",
- "description": "TypeGPU specific linting rules for ESLint",
- "keywords": [
- "eslint",
- "eslint-plugin",
- "eslintplugin",
- "typegpu",
- "use gpu"
- ],
+ "name": "eslint-plugin-internal",
+ "version": "0.10.0",
+ "private": true,
+ "description": "Ruleset for working on TypeGPU monorepo",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/software-mansion/TypeGPU.git#main",
- "directory": "packages/eslint-plugin"
+ "directory": "packages/eslint-plugin-internal"
},
- "files": [
- "dist"
- ],
"type": "module",
"sideEffects": false,
"main": "./src/index.ts",
@@ -27,24 +18,8 @@
"import": "./src/index.ts"
}
},
- "publishConfig": {
- "exports": {
- ".": {
- "types": "./dist/index.d.mts",
- "import": "./dist/index.mjs"
- }
- },
- "main": "./dist/index.mjs",
- "types": "./dist/index.d.mts"
- },
"scripts": {
- "build": "tsdown",
- "test:types": "pnpm tsc --p ./tsconfig.json --noEmit",
- "docs:init": "eslint-doc-generator --init-rule-docs",
- "docs:check": "eslint-doc-generator --check",
- "docs:update": "eslint-doc-generator",
- "test": "vitest",
- "prepublishOnly": "pnpm run docs:check && pnpm run test:types && vitest run && pnpm run build"
+ "test:types": "pnpm tsc --p ./tsconfig.json --noEmit"
},
"dependencies": {
"@typescript-eslint/utils": "^8.57.2"
diff --git a/packages/eslint-plugin-internal/src/configs.ts b/packages/eslint-plugin-internal/src/configs.ts
deleted file mode 100644
index df130b1dcd..0000000000
--- a/packages/eslint-plugin-internal/src/configs.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import type { TSESLint } from '@typescript-eslint/utils';
-import { noIntegerDivision } from './rules/noIntegerDivision.ts';
-import { noUnwrappedObjects } from './rules/noUnwrappedObjects.ts';
-import { noMath } from './rules/noMath.ts';
-import { noUninitializedVariables } from './rules/noUninitializedVariables.ts';
-import { noInvalidAssignment } from './rules/noInvalidAssignment.ts';
-
-export const rules = {
- 'no-integer-division': noIntegerDivision,
- 'no-unwrapped-objects': noUnwrappedObjects,
- 'no-uninitialized-variables': noUninitializedVariables,
- 'no-math': noMath,
- 'no-invalid-assignment': noInvalidAssignment,
-} as const;
-
-type Rules = Record<`typegpu/${keyof typeof rules}`, TSESLint.FlatConfig.RuleEntry>;
-
-export const recommendedRules: Rules = {
- 'typegpu/no-integer-division': 'warn',
- 'typegpu/no-unwrapped-objects': 'error',
- 'typegpu/no-uninitialized-variables': 'error',
- 'typegpu/no-math': 'warn',
- 'typegpu/no-invalid-assignment': 'error',
-};
-
-export const allRules: Rules = {
- 'typegpu/no-integer-division': 'error',
- 'typegpu/no-unwrapped-objects': 'error',
- 'typegpu/no-uninitialized-variables': 'error',
- 'typegpu/no-math': 'error',
- 'typegpu/no-invalid-assignment': 'error',
-};
diff --git a/packages/eslint-plugin-internal/src/enhanceRule.ts b/packages/eslint-plugin-internal/src/enhanceRule.ts
deleted file mode 100644
index 5d0a366b68..0000000000
--- a/packages/eslint-plugin-internal/src/enhanceRule.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import type { RuleContext, RuleListener } from '@typescript-eslint/utils/ts-eslint';
-
-export type RuleEnhancer = (context: RuleContext) => {
- visitors: RuleListener;
- state: TState;
-};
-
-type State>> = {
- [K in keyof TMap]: TMap[K] extends RuleEnhancer ? S : never;
-};
-
-/**
- * Allows enhancing rule code with additional context provided by RuleEnhancers (reusable node visitors collecting data).
- * @param enhancers a record of RuleEnhancers
- * @param rule a visitor with an additional `state` argument that allows access to the enhancers' data
- * @returns a resulting `(context: Context) => RuleListener` function
- *
- * @example
- * // inside of `createRule`
- * create: enhanceRule({ metadata: metadataTrackingEnhancer }, (context, state) => {
- * const { metadata } = state;
- *
- * return {
- * ObjectExpression(node) {
- * if (metadata.shouldReport()) {
- * context.report({ node, messageId: 'error' });
- * }
- * },
- * };
- */
-export function enhanceRule<
- TMap extends Record>,
- Context extends RuleContext,
->(enhancers: TMap, rule: (context: Context, state: State) => RuleListener) {
- return (context: Context) => {
- const enhancerVisitors: RuleListener[] = [];
- const combinedState: Record = {};
-
- for (const [key, enhancer] of Object.entries(enhancers)) {
- const initializedEnhancer = enhancer(context);
- enhancerVisitors.push(initializedEnhancer.visitors);
- combinedState[key] = initializedEnhancer.state;
- }
-
- const initializedRule = rule(context, combinedState as State);
-
- return mergeVisitors([...enhancerVisitors, initializedRule]);
- };
-}
-
-/**
- * Merges all passed visitors into one visitor.
- * Retains visitor order:
- * - on node enter, visitors are called in `visitorsList` order,
- * - on node exit, visitors are called in reversed order.
- */
-function mergeVisitors(visitors: RuleListener[]): RuleListener {
- const merged: RuleListener = {};
-
- const allKeys = new Set(visitors.flatMap((v) => Object.keys(v)));
-
- for (const key of allKeys) {
- const listeners = visitors.map((v) => v[key]).filter((fn) => fn !== undefined);
-
- if (listeners.length === 0) {
- continue;
- }
-
- // Reverse order if node is an exit node
- if (key.endsWith(':exit')) {
- listeners.reverse();
- }
-
- merged[key] = (...args: unknown[]) => {
- listeners.forEach((fn) => (fn as (...args: unknown[]) => void)(...args));
- };
- }
-
- return merged;
-}
diff --git a/packages/eslint-plugin-internal/src/enhancers/directiveTracking.ts b/packages/eslint-plugin-internal/src/enhancers/directiveTracking.ts
deleted file mode 100644
index b490760afb..0000000000
--- a/packages/eslint-plugin-internal/src/enhancers/directiveTracking.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-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 = {
- getEnclosingTypegpuFunction: () => FunctionNode | undefined;
-};
-
-/**
- * A RuleEnhancer that tracks whether the current node is inside a 'use gpu' function.
- *
- * @privateRemarks
- * Should the need arise, the API could be updated to expose:
- * - a list of directives of the current function,
- * - directives of other visited functions,
- * - top level directives.
- */
-export const directiveTracking: RuleEnhancer = () => {
- const stack: { node: FunctionNode; directives: string[] }[] = [];
-
- const visitors: RuleListener = {
- FunctionDeclaration(node) {
- stack.push({ node, directives: getDirectives(node) });
- },
- FunctionExpression(node) {
- stack.push({ node, directives: getDirectives(node) });
- },
- ArrowFunctionExpression(node) {
- stack.push({ node, directives: getDirectives(node) });
- },
-
- 'FunctionDeclaration:exit'() {
- stack.pop();
- },
- 'FunctionExpression:exit'() {
- stack.pop();
- },
- 'ArrowFunctionExpression:exit'() {
- stack.pop();
- },
- };
-
- return {
- visitors,
- state: {
- getEnclosingTypegpuFunction: () => {
- const current = stack.at(-1);
- if (current && current.directives.includes('use gpu')) {
- return current.node;
- }
- return undefined;
- },
- },
- };
-};
-
-function getDirectives(
- node:
- | TSESTree.FunctionDeclaration
- | TSESTree.FunctionExpression
- | TSESTree.ArrowFunctionExpression,
-): string[] {
- const body = node.body;
- if (body.type !== 'BlockStatement') {
- return [];
- }
-
- const directives: string[] = [];
- for (const statement of body.body) {
- if (statement.type === 'ExpressionStatement' && statement.directive) {
- directives.push(statement.directive);
- } else {
- break;
- }
- }
-
- return directives;
-}
diff --git a/packages/eslint-plugin-internal/src/index.ts b/packages/eslint-plugin-internal/src/index.ts
index 08763811d3..5e4ceb23ff 100644
--- a/packages/eslint-plugin-internal/src/index.ts
+++ b/packages/eslint-plugin-internal/src/index.ts
@@ -1,33 +1,15 @@
import pkg from '../package.json' with { type: 'json' };
import type { TSESLint } from '@typescript-eslint/utils';
-import { allRules, recommendedRules, rules } from './configs.ts';
+import { noMath } from './rules/noMath.ts';
-const pluginBase: TSESLint.FlatConfig.Plugin = {
+const plugin = {
meta: {
name: pkg.name,
version: pkg.version,
},
- rules,
-};
-
-const recommended: TSESLint.FlatConfig.Config = {
- name: 'typegpu/recommended',
- plugins: { typegpu: pluginBase },
- rules: recommendedRules,
-};
-
-const all: TSESLint.FlatConfig.Config = {
- name: 'typegpu/all',
- plugins: { typegpu: pluginBase },
- rules: allRules,
-};
-
-const plugin: TSESLint.FlatConfig.Plugin = {
- ...pluginBase,
- configs: {
- recommended,
- all,
+ rules: {
+ 'no-math': noMath,
},
-};
+} satisfies TSESLint.FlatConfig.Plugin;
export default plugin;
diff --git a/packages/eslint-plugin-internal/src/nodeHelpers.ts b/packages/eslint-plugin-internal/src/nodeHelpers.ts
deleted file mode 100644
index 4122427d17..0000000000
--- a/packages/eslint-plugin-internal/src/nodeHelpers.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { TSESTree } from '@typescript-eslint/utils';
-
-const transparentNodes = [
- 'TSAsExpression',
- 'TSSatisfiesExpression',
- 'TSTypeAssertion',
- 'TSNonNullExpression',
-];
-
-export function isTransparent(node: TSESTree.Node): boolean {
- return transparentNodes.includes(node.type);
-}
-
-export function getNonTransparentParent(node: TSESTree.Node) {
- let parent = node.parent;
- while (parent && isTransparent(parent)) {
- parent = parent.parent;
- }
- return parent;
-}
diff --git a/packages/eslint-plugin-internal/src/ruleCreator.ts b/packages/eslint-plugin-internal/src/ruleCreator.ts
index 6d07477ffc..d4fa2d5089 100644
--- a/packages/eslint-plugin-internal/src/ruleCreator.ts
+++ b/packages/eslint-plugin-internal/src/ruleCreator.ts
@@ -1,6 +1,5 @@
import { ESLintUtils } from '@typescript-eslint/utils';
export const createRule = ESLintUtils.RuleCreator(
- // TODO: docs for lint rules
() => `https://docs.swmansion.com/TypeGPU/getting-started/`,
);
diff --git a/packages/eslint-plugin-internal/src/rules/noIntegerDivision.ts b/packages/eslint-plugin-internal/src/rules/noIntegerDivision.ts
deleted file mode 100644
index 99664f29c7..0000000000
--- a/packages/eslint-plugin-internal/src/rules/noIntegerDivision.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import type { TSESTree } from '@typescript-eslint/utils';
-import { createRule } from '../ruleCreator.ts';
-
-// TODO: detect `std.div(d.u32(1), d.u32(2))`
-export const noIntegerDivision = createRule({
- name: 'no-integer-division',
- meta: {
- type: 'suggestion',
- docs: { description: `Disallow division incorporating numbers wrapped in 'u32' and 'i32'` },
- messages: {
- suspiciousDivision:
- "'{{snippet}}' might result in floating point values. To perform integer division, wrap the result in 'd.u32' or 'd.i32' instead",
- },
- schema: [],
- },
- defaultOptions: [],
-
- create(context) {
- return {
- BinaryExpression(node) {
- if (node.operator !== '/') {
- return;
- }
-
- if (node.parent?.type === 'CallExpression' && isIntCast(node.parent)) {
- return;
- }
-
- if (isIntCast(node.left) || isIntCast(node.right)) {
- context.report({
- node,
- messageId: 'suspiciousDivision',
- data: { snippet: context.sourceCode.getText(node) },
- });
- }
- },
- };
- },
-});
-
-/**
- * Checks if a node is a call expression to an integer cast function (i32 or u32).
- *
- * @example
- * // for simplicity, using code snippets instead of ASTs
- * isIntCasts('d.u32()'); // true
- * isIntCasts('i32()'); // true
- * isIntCasts('f32()'); // false
- */
-function isIntCast(node: TSESTree.Expression): boolean {
- if (node.type !== 'CallExpression') {
- return false;
- }
-
- let callee: TSESTree.Node = node.callee;
- while (callee.type === 'MemberExpression') {
- callee = callee.property;
- }
-
- return callee.type === 'Identifier' && ['i32', 'u32'].includes(callee.name);
-}
diff --git a/packages/eslint-plugin-internal/src/rules/noInvalidAssignment.ts b/packages/eslint-plugin-internal/src/rules/noInvalidAssignment.ts
deleted file mode 100644
index 80d4cbf278..0000000000
--- a/packages/eslint-plugin-internal/src/rules/noInvalidAssignment.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-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 type { RuleContext } from '@typescript-eslint/utils/ts-eslint';
-
-export const noInvalidAssignment = createRule({
- name: 'no-invalid-assignment',
- meta: {
- type: 'problem',
- docs: {
- description: `Disallow assignments that will generate invalid WGSL`,
- },
- 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: [],
- },
- defaultOptions: [],
-
- create: enhanceRule({ directives: directiveTracking }, (context, state) => {
- const { directives } = state;
-
- return {
- UpdateExpression(node) {
- const enclosingFn = directives.getEnclosingTypegpuFunction();
- validateAssignment(context, node, enclosingFn, node.argument);
- },
-
- AssignmentExpression(node) {
- const enclosingFn = directives.getEnclosingTypegpuFunction();
- validateAssignment(context, node, enclosingFn, node.left);
- },
- };
- }),
-});
-
-function validateAssignment(
- context: Readonly>,
- node: TSESTree.Node,
- enclosingFn: TSESTree.Node | undefined,
- leftNode: TSESTree.Node,
-) {
- if (!enclosingFn) {
- return;
- }
-
- // follow the member expression chain
- 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;
- }
-
- // look for a scope that defines the variable
- const variable = ASTUtils.findVariable(context.sourceCode.getScope(assignee), assignee);
- // defs is an array because there may be multiple definitions with `var`
- const def = variable?.defs[0];
-
- // 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') {
- context.report({
- messageId: 'parameterAssignment',
- node,
- data: { snippet: context.sourceCode.getText(leftNode) },
- });
- }
-}
diff --git a/packages/eslint-plugin-internal/src/rules/noMath.ts b/packages/eslint-plugin-internal/src/rules/noMath.ts
index 3a70cb3dda..1a79bfb9dc 100644
--- a/packages/eslint-plugin-internal/src/rules/noMath.ts
+++ b/packages/eslint-plugin-internal/src/rules/noMath.ts
@@ -1,8 +1,4 @@
import { createRule } from '../ruleCreator.ts';
-import { enhanceRule } from '../enhanceRule.ts';
-import { directiveTracking } from '../enhancers/directiveTracking.ts';
-import type { RuleContext } from '@typescript-eslint/utils/ts-eslint';
-import { ASTUtils, type TSESTree } from '@typescript-eslint/utils';
export const noMath = createRule({
name: 'no-math',
@@ -19,39 +15,15 @@ export const noMath = createRule({
},
defaultOptions: [],
- create: enhanceRule({ directives: directiveTracking }, (context, state) => {
- const { directives } = state;
-
+ create(context) {
return {
CallExpression(node) {
- if (!directives.getEnclosingTypegpuFunction()) {
- return;
- }
-
- if (
- node.callee.type === 'MemberExpression' &&
- node.callee.object.type === 'Identifier' &&
- node.callee.object.name === 'Math' &&
- isGlobalIdentifier(context, node.callee.object)
- ) {
- context.report({
- node,
- messageId: 'unexpected',
- data: { snippet: context.sourceCode.getText(node) },
- });
- }
+ context.report({
+ node,
+ messageId: 'unexpected',
+ data: { snippet: context.sourceCode.getText(node) },
+ });
},
};
- }),
+ },
});
-
-function isGlobalIdentifier(
- context: Readonly>,
- node: TSESTree.Identifier,
-) {
- const variable = ASTUtils.findVariable(context.sourceCode.getScope(node), node);
- if (!variable) {
- throw new Error(`Couldn't find variable ${node.name}.`);
- }
- return variable.defs.length === 0;
-}
diff --git a/packages/eslint-plugin-internal/src/rules/noUninitializedVariables.ts b/packages/eslint-plugin-internal/src/rules/noUninitializedVariables.ts
deleted file mode 100644
index c889ca1b2d..0000000000
--- a/packages/eslint-plugin-internal/src/rules/noUninitializedVariables.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { enhanceRule } from '../enhanceRule.ts';
-import { directiveTracking } from '../enhancers/directiveTracking.ts';
-import { createRule } from '../ruleCreator.ts';
-
-export const noUninitializedVariables = createRule({
- name: 'no-uninitialized-variables',
- meta: {
- type: 'problem',
- docs: {
- description: `Disallow variable declarations without initializers inside 'use gpu' functions`,
- },
- messages: {
- uninitializedVariable: "'{{snippet}}' must have an initial value",
- },
- schema: [],
- },
- defaultOptions: [],
-
- create: enhanceRule({ directives: directiveTracking }, (context, state) => {
- const { directives } = state;
-
- return {
- VariableDeclarator(node) {
- if (!directives.getEnclosingTypegpuFunction()) {
- return;
- }
- if (node.parent?.parent?.type === 'ForOfStatement') {
- // one exception where we allow uninitialized variable
- return;
- }
- if (node.init === null) {
- context.report({
- node,
- messageId: 'uninitializedVariable',
- data: { snippet: context.sourceCode.getText(node) },
- });
- }
- },
- };
- }),
-});
diff --git a/packages/eslint-plugin-internal/src/rules/noUnwrappedObjects.ts b/packages/eslint-plugin-internal/src/rules/noUnwrappedObjects.ts
deleted file mode 100644
index 99f6b4d180..0000000000
--- a/packages/eslint-plugin-internal/src/rules/noUnwrappedObjects.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { enhanceRule } from '../enhanceRule.ts';
-import { directiveTracking } from '../enhancers/directiveTracking.ts';
-import { getNonTransparentParent } from '../nodeHelpers.ts';
-import { createRule } from '../ruleCreator.ts';
-
-export const noUnwrappedObjects = createRule({
- name: 'no-unwrapped-objects',
- meta: {
- type: 'problem',
- docs: {
- description: `Disallow unwrapped Plain Old JavaScript Objects inside 'use gpu' functions (except returns)`,
- },
- messages: {
- unexpected: '{{snippet}} must be wrapped in a schema call',
- },
- schema: [],
- },
- defaultOptions: [],
-
- create: enhanceRule({ directives: directiveTracking }, (context, state) => {
- const { directives } = state;
-
- return {
- ObjectExpression(node) {
- if (!directives.getEnclosingTypegpuFunction()) {
- return;
- }
- let parent = getNonTransparentParent(node);
- if (parent?.type === 'Property') {
- // a part of a bigger struct
- return;
- }
- if (parent?.type === 'CallExpression') {
- // wrapped in a schema call
- return;
- }
- if (parent?.type === 'ReturnStatement') {
- // likely inferred (shelled fn or shell-less entry) so we cannot report
- return;
- }
- context.report({
- node,
- messageId: 'unexpected',
- data: { snippet: context.sourceCode.getText(node) },
- });
- },
- };
- }),
-});
diff --git a/packages/eslint-plugin-internal/tests/ruleNames.test.ts b/packages/eslint-plugin-internal/tests/ruleNames.test.ts
deleted file mode 100644
index d0c9f968cc..0000000000
--- a/packages/eslint-plugin-internal/tests/ruleNames.test.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { expect, it } from 'vitest';
-import { rules } from '../src/configs.ts';
-
-it('uses the same names for rules and exports', () => {
- for (const key in rules) {
- expect(rules[key as keyof typeof rules].name).toBe(key);
- }
-});
diff --git a/packages/eslint-plugin-internal/tests/rules/noIntegerDivision.test.ts b/packages/eslint-plugin-internal/tests/rules/noIntegerDivision.test.ts
deleted file mode 100644
index 509b8e1f99..0000000000
--- a/packages/eslint-plugin-internal/tests/rules/noIntegerDivision.test.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { describe } from 'vitest';
-import { noIntegerDivision } from '../../src/rules/noIntegerDivision.ts';
-import { ruleTester } from '../utils/ruleTester.ts';
-
-describe('noIntegerDivision', () => {
- ruleTester.run('noIntegerDivision', noIntegerDivision, {
- valid: ['1 / 2', 'd.u32(d.u32(1) / d.u32(2))'],
- invalid: [
- {
- code: 'd.u32(1) / 2',
- errors: [{ messageId: 'suspiciousDivision', data: { snippet: 'd.u32(1) / 2' } }],
- },
- {
- code: '1 / d.u32(2)',
- errors: [{ messageId: 'suspiciousDivision', data: { snippet: '1 / d.u32(2)' } }],
- },
- {
- code: 'd.u32(1) / d.u32(2)',
- errors: [{ messageId: 'suspiciousDivision', data: { snippet: 'd.u32(1) / d.u32(2)' } }],
- },
- {
- code: 'd.i32(1) / d.i32(2)',
- errors: [{ messageId: 'suspiciousDivision', data: { snippet: 'd.i32(1) / d.i32(2)' } }],
- },
- {
- code: 'd.u32(1) / d.i32(2)',
- errors: [{ messageId: 'suspiciousDivision', data: { snippet: 'd.u32(1) / d.i32(2)' } }],
- },
- {
- code: 'u32(1) / u32(2)',
- errors: [{ messageId: 'suspiciousDivision', data: { snippet: 'u32(1) / u32(2)' } }],
- },
- {
- code: 'd.u32(1) / d.u32(2) / d.u32(3)',
- errors: [
- {
- messageId: 'suspiciousDivision',
- data: { snippet: 'd.u32(1) / d.u32(2) / d.u32(3)' },
- },
- { messageId: 'suspiciousDivision', data: { snippet: 'd.u32(1) / d.u32(2)' } },
- ],
- },
- ],
- });
-});
diff --git a/packages/eslint-plugin-internal/tests/rules/noInvalidAssignment.test.ts b/packages/eslint-plugin-internal/tests/rules/noInvalidAssignment.test.ts
deleted file mode 100644
index b06d16a33a..0000000000
--- a/packages/eslint-plugin-internal/tests/rules/noInvalidAssignment.test.ts
+++ /dev/null
@@ -1,210 +0,0 @@
-import { describe } from 'vitest';
-import { ruleTester } from '../utils/ruleTester.ts';
-import { noInvalidAssignment } from '../../src/rules/noInvalidAssignment.ts';
-
-describe('noInvalidAssignment', () => {
- ruleTester.run('noInvalidAssignment', noInvalidAssignment, {
- 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; }',
-
- // 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; }",
- ],
- 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.prop = 1; }",
- errors: [
- {
- messageId: 'parameterAssignment',
- data: { snippet: 'a.prop' },
- },
- ],
- },
- {
- 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++; }",
- 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: "const fn = (a) => { 'use gpu'; a += 1; }",
- errors: [{ messageId: 'parameterAssignment', data: { snippet: 'a' } }],
- },
- {
- code: "const fn = (a) => { 'use gpu'; a.prop1.prop2 = 1; }",
- errors: [
- {
- messageId: 'parameterAssignment',
- data: { snippet: 'a.prop1.prop2' },
- },
- ],
- },
- {
- 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: [
- { messageId: 'parameterAssignment', data: { snippet: 'a' } },
- { messageId: 'parameterAssignment', data: { snippet: 'b' } },
- ],
- },
- {
- code: "const fn = (a) => { 'use gpu'; a.$prop = 1; }",
- errors: [
- {
- messageId: 'parameterAssignment',
- data: { snippet: 'a.$prop' },
- },
- ],
- },
- ],
- });
-
- ruleTester.run('invalidAssignment', noInvalidAssignment, {
- 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 }',
-
- // 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 }",
- ],
- invalid: [
- {
- code: "let a; const fn = () => { 'use gpu'; a = 1 }",
- errors: [{ messageId: 'jsAssignment', data: { snippet: 'a' } }],
- },
- {
- code: "var 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 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 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; }",
- 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' },
- },
- ],
- },
- {
- code: "const fn = () => { 'use gpu'; globalThis.prop = 1 }",
- errors: [
- {
- messageId: 'jsAssignment',
- data: { snippet: 'globalThis.prop' },
- },
- ],
- },
- ],
- });
-});
diff --git a/packages/eslint-plugin-internal/tests/rules/noMath.test.ts b/packages/eslint-plugin-internal/tests/rules/noMath.test.ts
deleted file mode 100644
index 3ddd2708f8..0000000000
--- a/packages/eslint-plugin-internal/tests/rules/noMath.test.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { describe } from 'vitest';
-import { ruleTester } from '../utils/ruleTester.ts';
-import { noMath } from '../../src/rules/noMath.ts';
-
-describe('noMath', () => {
- ruleTester.run('noMath', noMath, {
- valid: [
- 'const result = Math.sin(1);',
- 'const t = std.sin(Math.PI)',
- "const fn = () => { 'use gpu'; const vec = std.sin(Math.PI); }",
- "const Math = { sin: std.sin }; const fn = () => { 'use gpu'; const vec = Math.sin(0); }",
- "import Math from 'utils'; const fn = () => { 'use gpu'; const vec = Math.sin(0); }",
- ],
- invalid: [
- {
- code: "const fn = () => { 'use gpu'; const vec = Math.sin(0); }",
- errors: [
- {
- messageId: 'unexpected',
- data: { snippet: 'Math.sin(0)' },
- },
- ],
- },
- ],
- });
-});
diff --git a/packages/eslint-plugin-internal/tests/rules/noUninitializedVariables.test.ts b/packages/eslint-plugin-internal/tests/rules/noUninitializedVariables.test.ts
deleted file mode 100644
index 96dc6ca5c3..0000000000
--- a/packages/eslint-plugin-internal/tests/rules/noUninitializedVariables.test.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { describe } from 'vitest';
-import { ruleTester } from '../utils/ruleTester.ts';
-import { noUninitializedVariables } from '../../src/rules/noUninitializedVariables.ts';
-
-describe('noUninitializedVariables', () => {
- ruleTester.run('noUninitializedVariables', noUninitializedVariables, {
- valid: [
- 'let a;',
- 'let a, b;',
- "const fn = () => { 'use gpu'; const vec = d.vec3f(); }",
- "const fn = () => { 'use gpu'; let vec = d.vec3f(); }",
- `const fn = () => { 'use gpu';
- let a = 0;
- for (const foo of tgpu.unroll([1, 2, 3])) {
- a += foo;
- }
- }`,
- ],
- invalid: [
- {
- code: "const fn = () => { 'use gpu'; let vec; }",
- errors: [
- {
- messageId: 'uninitializedVariable',
- data: { snippet: 'vec' },
- },
- ],
- },
- {
- code: "const fn = () => { 'use gpu'; let a = 1, b, c = d.vec3f(), d; }",
- errors: [
- {
- messageId: 'uninitializedVariable',
- data: { snippet: 'b' },
- },
- {
- messageId: 'uninitializedVariable',
- data: { snippet: 'd' },
- },
- ],
- },
- ],
- });
-});
diff --git a/packages/eslint-plugin-internal/tests/rules/noUnwrappedObjects.test.ts b/packages/eslint-plugin-internal/tests/rules/noUnwrappedObjects.test.ts
deleted file mode 100644
index 1ca1e919a5..0000000000
--- a/packages/eslint-plugin-internal/tests/rules/noUnwrappedObjects.test.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import { describe } from 'vitest';
-import { ruleTester } from '../utils/ruleTester.ts';
-import { noUnwrappedObjects } from '../../src/rules/noUnwrappedObjects.ts';
-
-describe('noUnwrappedObjects', () => {
- ruleTester.run('noUnwrappedObjects', noUnwrappedObjects, {
- valid: [
- // correctly wrapped
- "function func() { 'use gpu'; const wrapped = Schema({ a: 1 }); }",
- "const func = function() { 'use gpu'; const wrapped = Schema({ a: 1 }); }",
- "() => { 'use gpu'; const wrapped = Schema({ a: 1 }); }",
-
- // not inside 'use gpu' function
- 'const pojo = { a: 1 };',
- 'function func() { const unwrapped = { a: 1 }; }',
- 'const func = function () { const unwrapped = { a: 1 }; }',
- '() => { const unwrapped = { a: 1 }; }',
- 'function func() { return { a: 1 }; }',
- 'const func = function () { return { a: 1 }; }',
- '() => { return { a: 1 }; }',
-
- // return from 'use gpu' function
- "function func() { 'use gpu'; return { a: 1 }; }",
- "const func = function() { 'use gpu'; return { a: 1 }; }",
- "() => { 'use gpu'; return { a: 1 }; }",
- "() => { 'use gpu'; return { a: { b: 1 } }; }",
- "() => { 'use gpu'; return { a: 1 } as typeof Struct; }",
- "() => { 'use gpu'; return { a: 1 } satisfies Struct; }",
- "() => { 'use gpu'; return ({ a: 1 }); }",
- ],
- invalid: [
- {
- code: "function func() { 'use gpu'; const unwrapped = { a: 1 }; }",
- errors: [
- {
- messageId: 'unexpected',
- data: { snippet: '{ a: 1 }' },
- },
- ],
- },
- {
- code: "const func = function() { 'use gpu'; const unwrapped = { a: 1 }; }",
- errors: [
- {
- messageId: 'unexpected',
- data: { snippet: '{ a: 1 }' },
- },
- ],
- },
- {
- code: "() => { 'use gpu'; const unwrapped = { a: 1 }; }",
- errors: [
- {
- messageId: 'unexpected',
- data: { snippet: '{ a: 1 }' },
- },
- ],
- },
- {
- code: "function func() { 'unknown directive'; 'use gpu'; const unwrapped = { a: 1 }; }",
- errors: [
- {
- messageId: 'unexpected',
- data: { snippet: '{ a: 1 }' },
- },
- ],
- },
- {
- code: "() => { 'use gpu'; const unwrapped = { a: { b: 1 } }; }",
- errors: [
- {
- messageId: 'unexpected',
- data: { snippet: '{ a: { b: 1 } }' },
- },
- ],
- },
- ],
- });
-});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b8d9288ee0..2575719857 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -73,6 +73,9 @@ importers:
eslint-plugin-eslint-plugin:
specifier: ^7.3.2
version: 7.3.2(eslint@9.39.2(jiti@2.6.1))
+ eslint-plugin-internal:
+ specifier: workspace:*
+ version: link:packages/eslint-plugin-internal
eslint-plugin-typegpu:
specifier: workspace:*
version: link:packages/eslint-plugin
@@ -430,6 +433,34 @@ importers:
specifier: ^4.0.17
version: 4.0.18(@types/node@24.10.0)(@vitest/browser-preview@4.1.2(msw@2.10.2(@types/node@24.10.0)(tsover@5.9.11))(vite@8.0.2(@types/node@24.10.0)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@4.1.2))(esbuild@0.27.4)(jiti@2.6.1)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.8))(msw@2.10.2(@types/node@24.10.0)(tsover@5.9.11))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
+ packages/eslint-plugin-internal:
+ dependencies:
+ '@typescript-eslint/utils':
+ specifier: ^8.57.2
+ version: 8.57.2(eslint@9.39.2(jiti@2.6.1))(tsover@5.9.11)
+ devDependencies:
+ '@types/node':
+ specifier: ^24.1.0
+ version: 24.10.0
+ '@typescript-eslint/rule-tester':
+ specifier: ^8.57.2
+ version: 8.57.2(eslint@9.39.2(jiti@2.6.1))(tsover@5.9.11)
+ eslint:
+ specifier: ^9.39.2
+ version: 9.39.2(jiti@2.6.1)
+ eslint-doc-generator:
+ specifier: ^3.3.2
+ version: 3.3.2(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.0)(tsover@5.9.11)
+ tsdown:
+ specifier: ^0.20.3
+ version: 0.20.3(tsover@5.9.11)
+ typescript:
+ specifier: npm:tsover@^5.9.11
+ version: tsover@5.9.11
+ vitest:
+ specifier: ^4.0.17
+ version: 4.1.2(@types/node@24.10.0)(@vitest/browser-preview@4.1.2)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.8))(msw@2.10.2(@types/node@24.10.0)(tsover@5.9.11))(vite@8.0.2(@types/node@24.10.0)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
+
packages/tgpu-dev-cli:
dependencies:
arg:
From 2916f6725b65b24c3a0391a744fbb68491084a7f Mon Sep 17 00:00:00 2001
From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com>
Date: Fri, 3 Apr 2026 12:16:09 +0200
Subject: [PATCH 03/12] Implement noUselessPathSegments
---
oxlint.config.ts | 2 +-
.../.eslint-doc-generatorrc.mjs | 9 ---
packages/eslint-plugin-internal/package.json | 3 +-
packages/eslint-plugin-internal/src/index.ts | 4 +-
.../src/rules/noMath.ts | 29 ---------
.../src/rules/noUselessPathSegments.ts | 50 ++++++++++++++
.../tests/rules/noUselessPathSegments.test.ts | 65 +++++++++++++++++++
7 files changed, 120 insertions(+), 42 deletions(-)
delete mode 100644 packages/eslint-plugin-internal/.eslint-doc-generatorrc.mjs
delete mode 100644 packages/eslint-plugin-internal/src/rules/noMath.ts
create mode 100644 packages/eslint-plugin-internal/src/rules/noUselessPathSegments.ts
create mode 100644 packages/eslint-plugin-internal/tests/rules/noUselessPathSegments.test.ts
diff --git a/oxlint.config.ts b/oxlint.config.ts
index 07521e72e9..6e02ac71c1 100644
--- a/oxlint.config.ts
+++ b/oxlint.config.ts
@@ -24,7 +24,7 @@ export default defineConfig({
'eslint-plugin-import/no-named-as-default': 'off',
'eslint-plugin-import/no-named-as-default-member': 'off',
'eslint-plugin-import/extensions': ['error', 'always', { ignorePackages: true }],
- 'eslint-plugin-internal/no-math': 'error',
+ 'eslint-plugin-internal/no-useless-path-segments': 'error',
},
ignorePatterns: ['**/*.astro', '**/*.mjs'],
overrides: [
diff --git a/packages/eslint-plugin-internal/.eslint-doc-generatorrc.mjs b/packages/eslint-plugin-internal/.eslint-doc-generatorrc.mjs
deleted file mode 100644
index 0604c2e099..0000000000
--- a/packages/eslint-plugin-internal/.eslint-doc-generatorrc.mjs
+++ /dev/null
@@ -1,9 +0,0 @@
-const config = {
- ignoreConfig: ['all'],
- configEmoji: [['recommended', '⭐']],
- postprocess: (content) => {
- return content.replaceAll('💼', '🚨').replaceAll('🚫', '💤');
- },
-};
-
-export default config;
diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json
index 0ed80ab20d..53c342f4f9 100644
--- a/packages/eslint-plugin-internal/package.json
+++ b/packages/eslint-plugin-internal/package.json
@@ -19,7 +19,8 @@
}
},
"scripts": {
- "test:types": "pnpm tsc --p ./tsconfig.json --noEmit"
+ "test:types": "pnpm tsc --p ./tsconfig.json --noEmit",
+ "test": "vitest"
},
"dependencies": {
"@typescript-eslint/utils": "^8.57.2"
diff --git a/packages/eslint-plugin-internal/src/index.ts b/packages/eslint-plugin-internal/src/index.ts
index 5e4ceb23ff..229a218fbc 100644
--- a/packages/eslint-plugin-internal/src/index.ts
+++ b/packages/eslint-plugin-internal/src/index.ts
@@ -1,6 +1,6 @@
import pkg from '../package.json' with { type: 'json' };
import type { TSESLint } from '@typescript-eslint/utils';
-import { noMath } from './rules/noMath.ts';
+import { noUselessPathSegments } from './rules/noUselessPathSegments.ts';
const plugin = {
meta: {
@@ -8,7 +8,7 @@ const plugin = {
version: pkg.version,
},
rules: {
- 'no-math': noMath,
+ 'no-useless-path-segments': noUselessPathSegments,
},
} satisfies TSESLint.FlatConfig.Plugin;
diff --git a/packages/eslint-plugin-internal/src/rules/noMath.ts b/packages/eslint-plugin-internal/src/rules/noMath.ts
deleted file mode 100644
index 1a79bfb9dc..0000000000
--- a/packages/eslint-plugin-internal/src/rules/noMath.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { createRule } from '../ruleCreator.ts';
-
-export const noMath = createRule({
- name: 'no-math',
- meta: {
- type: 'suggestion',
- docs: {
- description: `Disallow usage of JavaScript 'Math' methods inside 'use gpu' functions`,
- },
- messages: {
- unexpected:
- "Using Math methods, such as '{{snippet}}', may not work as expected. Use 'std' instead",
- },
- schema: [],
- },
- defaultOptions: [],
-
- create(context) {
- return {
- CallExpression(node) {
- context.report({
- node,
- messageId: 'unexpected',
- data: { snippet: context.sourceCode.getText(node) },
- });
- },
- };
- },
-});
diff --git a/packages/eslint-plugin-internal/src/rules/noUselessPathSegments.ts b/packages/eslint-plugin-internal/src/rules/noUselessPathSegments.ts
new file mode 100644
index 0000000000..3268555b63
--- /dev/null
+++ b/packages/eslint-plugin-internal/src/rules/noUselessPathSegments.ts
@@ -0,0 +1,50 @@
+import { createRule } from '../ruleCreator.ts';
+import * as path from 'node:path';
+
+export const noUselessPathSegments = createRule({
+ name: 'no-useless-path-segments',
+ meta: {
+ type: 'suggestion',
+ fixable: 'code',
+ docs: {
+ description: 'Disallow redundant parent folder lookups in relative import paths',
+ },
+ messages: {
+ redundant: "Import path '{{path}}' can be simplified to '{{simplified}}'",
+ },
+ schema: [],
+ },
+ defaultOptions: [],
+
+ create(context) {
+ return {
+ ImportDeclaration(node) {
+ const importPath = node.source.value;
+ if (!importPath.startsWith('.')) {
+ return;
+ }
+
+ const filename = context.filename; // e.g. `/Users/me/typegpu-monorepo/packages/typegpu/tests/buffer.test.ts`
+ const dir = path.dirname(filename); // e.g. `/Users/me/typegpu-monorepo/packages/typegpu/tests`
+ const resolved = path.resolve(dir, importPath); // e.g. `/Users/me/typegpu-monorepo/packages/typegpu/src/data/index.ts`
+ let simplified = path.relative(dir, resolved); // e.g. `../src/data/index.ts`, or `subfolder/helper.ts`
+
+ if (!simplified.startsWith('..')) {
+ simplified = `./${simplified}`;
+ }
+
+ if (importPath !== simplified) {
+ context.report({
+ node,
+ messageId: 'redundant',
+ data: { path: importPath, simplified },
+ fix(fixer) {
+ const quote = context.sourceCode.getText(node.source)[0];
+ return fixer.replaceText(node.source, `${quote}${simplified}${quote}`);
+ },
+ });
+ }
+ },
+ };
+ },
+});
diff --git a/packages/eslint-plugin-internal/tests/rules/noUselessPathSegments.test.ts b/packages/eslint-plugin-internal/tests/rules/noUselessPathSegments.test.ts
new file mode 100644
index 0000000000..fe2f73e6e6
--- /dev/null
+++ b/packages/eslint-plugin-internal/tests/rules/noUselessPathSegments.test.ts
@@ -0,0 +1,65 @@
+import { describe } from 'vitest';
+import { ruleTester } from '../utils/ruleTester.ts';
+import { noUselessPathSegments } from '../../src/rules/noUselessPathSegments.ts';
+
+const filename = '/Users/me/typegpu-monorepo/packages/typegpu/tests/buffer.test.ts';
+
+describe('noRedundantImportPath', () => {
+ ruleTester.run('noRedundantImportPath', noUselessPathSegments, {
+ valid: [
+ { code: "import item from './file.ts';", filename },
+ { code: "import item from '../file.ts';", filename },
+ { code: "import item from '../../file.ts';", filename },
+ { code: "import item from '../folder/file.ts';", filename },
+
+ { code: "import item from 'eslint-plugin-typegpu';", filename },
+ { code: "import item from '@eslint-plugin/typegpu';", filename },
+ ],
+ invalid: [
+ {
+ code: "import item from '../tests/file.ts';",
+ filename,
+ errors: [
+ {
+ messageId: 'redundant',
+ data: { path: '../tests/file.ts', simplified: './file.ts' },
+ },
+ ],
+ output: "import item from './file.ts';",
+ },
+ {
+ code: 'import item from "../tests/file.ts";',
+ filename,
+ errors: [
+ {
+ messageId: 'redundant',
+ data: { path: '../tests/file.ts', simplified: './file.ts' },
+ },
+ ],
+ output: 'import item from "./file.ts";',
+ },
+ {
+ code: "import item from './../file.ts';",
+ filename,
+ errors: [
+ {
+ messageId: 'redundant',
+ data: { path: './../file.ts', simplified: '../file.ts' },
+ },
+ ],
+ output: "import item from '../file.ts';",
+ },
+ {
+ code: "import item from '../../typegpu/folder/file.ts';",
+ filename,
+ errors: [
+ {
+ messageId: 'redundant',
+ data: { path: '../../typegpu/folder/file.ts', simplified: '../folder/file.ts' },
+ },
+ ],
+ output: "import item from '../folder/file.ts';",
+ },
+ ],
+ });
+});
From f2b4ca96bc68add38bb0f0048b0d85eda7c88ffa Mon Sep 17 00:00:00 2001
From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com>
Date: Fri, 3 Apr 2026 12:26:53 +0200
Subject: [PATCH 04/12] Implement noLongImports rule
---
oxlint.config.ts | 4 +++
packages/eslint-plugin-internal/src/index.ts | 2 ++
.../src/rules/noLongImports.ts | 33 +++++++++++++++++++
.../tests/rules/noLongImports.test.ts | 27 +++++++++++++++
.../tests/rules/noUselessPathSegments.test.ts | 4 +--
5 files changed, 68 insertions(+), 2 deletions(-)
create mode 100644 packages/eslint-plugin-internal/src/rules/noLongImports.ts
create mode 100644 packages/eslint-plugin-internal/tests/rules/noLongImports.test.ts
diff --git a/oxlint.config.ts b/oxlint.config.ts
index 6e02ac71c1..eb317c814a 100644
--- a/oxlint.config.ts
+++ b/oxlint.config.ts
@@ -46,6 +46,10 @@ export default defineConfig({
...(eslintPlugin.configs.recommended.rules as Record),
},
},
+ {
+ files: ['apps/typegpu-docs/src/examples/**/*.ts'],
+ rules: { 'eslint-plugin-internal/no-long-imports': 'error' },
+ },
],
env: {
builtin: true,
diff --git a/packages/eslint-plugin-internal/src/index.ts b/packages/eslint-plugin-internal/src/index.ts
index 229a218fbc..5fd5be5883 100644
--- a/packages/eslint-plugin-internal/src/index.ts
+++ b/packages/eslint-plugin-internal/src/index.ts
@@ -1,6 +1,7 @@
import pkg from '../package.json' with { type: 'json' };
import type { TSESLint } from '@typescript-eslint/utils';
import { noUselessPathSegments } from './rules/noUselessPathSegments.ts';
+import { noLongImports } from './rules/noLongImports.ts';
const plugin = {
meta: {
@@ -9,6 +10,7 @@ const plugin = {
},
rules: {
'no-useless-path-segments': noUselessPathSegments,
+ 'no-long-imports': noLongImports,
},
} satisfies TSESLint.FlatConfig.Plugin;
diff --git a/packages/eslint-plugin-internal/src/rules/noLongImports.ts b/packages/eslint-plugin-internal/src/rules/noLongImports.ts
new file mode 100644
index 0000000000..982fc1b5f1
--- /dev/null
+++ b/packages/eslint-plugin-internal/src/rules/noLongImports.ts
@@ -0,0 +1,33 @@
+import { createRule } from '../ruleCreator.ts';
+
+export const noLongImports = createRule({
+ name: 'no-long-imports',
+ meta: {
+ type: 'suggestion',
+ fixable: 'code',
+ docs: {
+ description: 'Disallow long import paths (to be used in TypeGPU examples), except common.',
+ },
+ messages: {
+ unexpected:
+ "Import path '{{path}}' probably won't work on StackBlitz, use imports from packages instead",
+ },
+ schema: [],
+ },
+ defaultOptions: [],
+
+ create(context) {
+ return {
+ ImportDeclaration(node) {
+ const importPath = node.source.value;
+ if (importPath.startsWith('../../') && !importPath.startsWith('../../common')) {
+ context.report({
+ node,
+ messageId: 'unexpected',
+ data: { path: importPath },
+ });
+ }
+ },
+ };
+ },
+});
diff --git a/packages/eslint-plugin-internal/tests/rules/noLongImports.test.ts b/packages/eslint-plugin-internal/tests/rules/noLongImports.test.ts
new file mode 100644
index 0000000000..69c89e7be8
--- /dev/null
+++ b/packages/eslint-plugin-internal/tests/rules/noLongImports.test.ts
@@ -0,0 +1,27 @@
+import { describe } from 'vitest';
+import { ruleTester } from '../utils/ruleTester.ts';
+import { noLongImports } from '../../src/rules/noLongImports.ts';
+
+const filename = '/Users/me/typegpu-monorepo/packages/typegpu/tests/buffer.test.ts';
+
+describe('noLongImports', () => {
+ ruleTester.run('noLongImports', noLongImports, {
+ valid: [
+ { code: "import item from './file.ts';", filename },
+ { code: "import item from '../file.ts';", filename },
+ { code: "import item from '../../common/file.ts';", filename },
+ ],
+ invalid: [
+ {
+ code: "import item from '../../file.ts';",
+ filename,
+ errors: [
+ {
+ messageId: 'unexpected',
+ data: { path: '../../file.ts' },
+ },
+ ],
+ },
+ ],
+ });
+});
diff --git a/packages/eslint-plugin-internal/tests/rules/noUselessPathSegments.test.ts b/packages/eslint-plugin-internal/tests/rules/noUselessPathSegments.test.ts
index fe2f73e6e6..1b539ca78f 100644
--- a/packages/eslint-plugin-internal/tests/rules/noUselessPathSegments.test.ts
+++ b/packages/eslint-plugin-internal/tests/rules/noUselessPathSegments.test.ts
@@ -4,8 +4,8 @@ import { noUselessPathSegments } from '../../src/rules/noUselessPathSegments.ts'
const filename = '/Users/me/typegpu-monorepo/packages/typegpu/tests/buffer.test.ts';
-describe('noRedundantImportPath', () => {
- ruleTester.run('noRedundantImportPath', noUselessPathSegments, {
+describe('noUselessPathSegments', () => {
+ ruleTester.run('noUselessPathSegments', noUselessPathSegments, {
valid: [
{ code: "import item from './file.ts';", filename },
{ code: "import item from '../file.ts';", filename },
From 64dc8ad8d5dbaacd14a2304af7c4461c03159239 Mon Sep 17 00:00:00 2001
From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com>
Date: Fri, 3 Apr 2026 12:31:36 +0200
Subject: [PATCH 05/12] nr fix
---
packages/typegpu/src/core/pipeline/computePipeline.ts | 2 +-
packages/typegpu/src/core/pipeline/renderPipeline.ts | 4 ++--
packages/typegpu/src/core/querySet/querySet.ts | 2 +-
packages/typegpu/src/core/root/init.ts | 6 +-----
packages/typegpu/src/core/root/rootTypes.ts | 4 ++--
packages/typegpu/src/core/slot/slotTypes.ts | 2 +-
packages/typegpu/src/core/unroll/tgpuUnroll.ts | 10 +++++-----
packages/typegpu/src/tgsl/generationHelpers.ts | 6 +++---
8 files changed, 16 insertions(+), 20 deletions(-)
diff --git a/packages/typegpu/src/core/pipeline/computePipeline.ts b/packages/typegpu/src/core/pipeline/computePipeline.ts
index 57cd50dd0a..b61e3d8457 100644
--- a/packages/typegpu/src/core/pipeline/computePipeline.ts
+++ b/packages/typegpu/src/core/pipeline/computePipeline.ts
@@ -1,5 +1,5 @@
import type { AnyComputeBuiltin } from '../../builtin.ts';
-import type { TgpuQuerySet } from '../../core/querySet/querySet.ts';
+import type { TgpuQuerySet } from '../querySet/querySet.ts';
import { type ResolvedSnippet, snip } from '../../data/snippet.ts';
import { sizeOf } from '../../data/sizeOf.ts';
import type { AnyWgslData } from '../../data/wgslTypes.ts';
diff --git a/packages/typegpu/src/core/pipeline/renderPipeline.ts b/packages/typegpu/src/core/pipeline/renderPipeline.ts
index 2bdfebb6dd..b7c6f14430 100644
--- a/packages/typegpu/src/core/pipeline/renderPipeline.ts
+++ b/packages/typegpu/src/core/pipeline/renderPipeline.ts
@@ -1,6 +1,6 @@
import type { AnyBuiltin, OmitBuiltins } from '../../builtin.ts';
-import type { IndexFlag, TgpuBuffer, VertexFlag } from '../../core/buffer/buffer.ts';
-import type { TgpuQuerySet } from '../../core/querySet/querySet.ts';
+import type { IndexFlag, TgpuBuffer, VertexFlag } from '../buffer/buffer.ts';
+import type { TgpuQuerySet } from '../querySet/querySet.ts';
import { isBuiltin } from '../../data/attributes.ts';
import { type Disarray, getCustomLocation, type UndecorateRecord } from '../../data/dataTypes.ts';
import { sizeOf } from '../../data/sizeOf.ts';
diff --git a/packages/typegpu/src/core/querySet/querySet.ts b/packages/typegpu/src/core/querySet/querySet.ts
index 547f337fbe..9d1322e3df 100644
--- a/packages/typegpu/src/core/querySet/querySet.ts
+++ b/packages/typegpu/src/core/querySet/querySet.ts
@@ -1,5 +1,5 @@
import { setName, type TgpuNamable } from '../../shared/meta.ts';
-import type { ExperimentalTgpuRoot } from '../../core/root/rootTypes.ts';
+import type { ExperimentalTgpuRoot } from '../root/rootTypes.ts';
import { $internal } from '../../shared/symbols.ts';
export interface TgpuQuerySet extends TgpuNamable {
diff --git a/packages/typegpu/src/core/root/init.ts b/packages/typegpu/src/core/root/init.ts
index 8fad9a5951..51aee4a391 100644
--- a/packages/typegpu/src/core/root/init.ts
+++ b/packages/typegpu/src/core/root/init.ts
@@ -1,9 +1,5 @@
import { type AnyComputeBuiltin, builtin, type OmitBuiltins } from '../../builtin.ts';
-import {
- INTERNAL_createQuerySet,
- isQuerySet,
- type TgpuQuerySet,
-} from '../../core/querySet/querySet.ts';
+import { INTERNAL_createQuerySet, isQuerySet, type TgpuQuerySet } from '../querySet/querySet.ts';
import type { AnyData, Disarray } from '../../data/dataTypes.ts';
import type { AnyWgslData, BaseData, v3u, Vec3u, WgslArray } from '../../data/wgslTypes.ts';
import { invariant } from '../../errors.ts';
diff --git a/packages/typegpu/src/core/root/rootTypes.ts b/packages/typegpu/src/core/root/rootTypes.ts
index 05b9c3db32..5a2b1167ec 100644
--- a/packages/typegpu/src/core/root/rootTypes.ts
+++ b/packages/typegpu/src/core/root/rootTypes.ts
@@ -1,5 +1,5 @@
import type { AnyComputeBuiltin, AnyFragmentInputBuiltin, OmitBuiltins } from '../../builtin.ts';
-import type { TgpuQuerySet } from '../../core/querySet/querySet.ts';
+import type { TgpuQuerySet } from '../querySet/querySet.ts';
import type { AnyData, Disarray, UndecorateRecord } from '../../data/dataTypes.ts';
import type { WgslComparisonSamplerProps, WgslSamplerProps } from '../../data/sampler.ts';
import type {
@@ -51,7 +51,7 @@ import type {
LayoutToAllowedAttribs,
} from '../vertexLayout/vertexAttribute.ts';
import type { TgpuVertexLayout } from '../vertexLayout/vertexLayout.ts';
-import type { TgpuComputeFn } from './../function/tgpuComputeFn.ts';
+import type { TgpuComputeFn } from '../function/tgpuComputeFn.ts';
import type { TgpuNamable } from '../../shared/meta.ts';
import type {
AnyAutoCustoms,
diff --git a/packages/typegpu/src/core/slot/slotTypes.ts b/packages/typegpu/src/core/slot/slotTypes.ts
index 80cab9197a..8fce92c90d 100644
--- a/packages/typegpu/src/core/slot/slotTypes.ts
+++ b/packages/typegpu/src/core/slot/slotTypes.ts
@@ -5,7 +5,7 @@ import type { GPUValueOf, Infer, InferGPU } from '../../shared/repr.ts';
import { $gpuValueOf, $internal, $providing } from '../../shared/symbols.ts';
import type { UnwrapRuntimeConstructor } from '../../tgpuBindGroupLayout.ts';
import type { TgpuBufferShorthand } from '../buffer/bufferShorthand.ts';
-import type { TgpuBufferUsage } from './../buffer/bufferUsage.ts';
+import type { TgpuBufferUsage } from '../buffer/bufferUsage.ts';
import type { TgpuConst } from '../constant/tgpuConstant.ts';
import type { Withable } from '../root/rootTypes.ts';
import type { TgpuTextureView } from '../texture/texture.ts';
diff --git a/packages/typegpu/src/core/unroll/tgpuUnroll.ts b/packages/typegpu/src/core/unroll/tgpuUnroll.ts
index 5f17ff0c2f..1b30fae7e2 100644
--- a/packages/typegpu/src/core/unroll/tgpuUnroll.ts
+++ b/packages/typegpu/src/core/unroll/tgpuUnroll.ts
@@ -1,9 +1,9 @@
import { stitch } from '../resolve/stitch.ts';
-import { $gpuCallable, $internal, $resolve } from '../../../src/shared/symbols.ts';
-import { setName } from '../../../src/shared/meta.ts';
-import type { DualFn } from '../../../src/types.ts';
-import { type ResolvedSnippet, snip, type Snippet } from '../../../src/data/snippet.ts';
-import type { ResolutionCtx, SelfResolvable } from '../../../src/types.ts';
+import { $gpuCallable, $internal, $resolve } from '../../shared/symbols.ts';
+import { setName } from '../../shared/meta.ts';
+import type { DualFn } from '../../types.ts';
+import { type ResolvedSnippet, snip, type Snippet } from '../../data/snippet.ts';
+import type { ResolutionCtx, SelfResolvable } from '../../types.ts';
import type { BaseData } from '../../data/wgslTypes.ts';
/**
diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts
index 628742269a..76835e7c33 100644
--- a/packages/typegpu/src/tgsl/generationHelpers.ts
+++ b/packages/typegpu/src/tgsl/generationHelpers.ts
@@ -26,9 +26,9 @@ import {
type SelfResolvable,
} from '../types.ts';
import type { ShelllessRepository } from './shellless.ts';
-import { stitch } from '../../src/core/resolve/stitch.ts';
-import { WgslTypeError } from '../../src/errors.ts';
-import { $internal, $resolve } from '../../src/shared/symbols.ts';
+import { stitch } from '../core/resolve/stitch.ts';
+import { WgslTypeError } from '../errors.ts';
+import { $internal, $resolve } from '../shared/symbols.ts';
export function numericLiteralToSnippet(value: number): Snippet {
if (value >= 2 ** 63 || value < -(2 ** 63)) {
From d4f5c1cff72d6843ce6438bdc35cd879360293ca Mon Sep 17 00:00:00 2001
From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com>
Date: Fri, 3 Apr 2026 12:39:20 +0200
Subject: [PATCH 06/12] Fix lockfile
---
pnpm-lock.yaml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e1c0e2a0c4..cb799d73dc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -431,7 +431,7 @@ importers:
version: tsover@5.9.11
vitest:
specifier: ^4.0.17
- version: 4.0.18(@types/node@24.10.0)(@vitest/browser-preview@4.1.2)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.8))(msw@2.10.2(@types/node@24.10.0)(tsover@5.9.11))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.3)
+ version: 4.0.18(@types/node@24.10.0)(@vitest/browser-preview@4.1.2(msw@2.10.2(@types/node@24.10.0)(tsover@5.9.11))(vite@8.0.2(@types/node@24.10.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.3))(vitest@4.1.2))(esbuild@0.27.5)(jiti@2.6.1)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.8))(msw@2.10.2(@types/node@24.10.0)(tsover@5.9.11))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.3)
packages/eslint-plugin-internal:
dependencies:
@@ -459,7 +459,7 @@ importers:
version: tsover@5.9.11
vitest:
specifier: ^4.0.17
- version: 4.1.2(@types/node@24.10.0)(@vitest/browser-preview@4.1.2)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.8))(msw@2.10.2(@types/node@24.10.0)(tsover@5.9.11))(vite@8.0.2(@types/node@24.10.0)(esbuild@0.27.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
+ version: 4.1.2(@types/node@24.10.0)(@vitest/browser-preview@4.1.2)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.8))(msw@2.10.2(@types/node@24.10.0)(tsover@5.9.11))(vite@8.0.2(@types/node@24.10.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.3))
packages/tgpu-dev-cli:
dependencies:
@@ -16092,7 +16092,7 @@ snapshots:
optionalDependencies:
vite: 8.0.2(@types/node@24.10.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.3)
- vitest@4.0.18(@types/node@24.10.0)(@vitest/browser-preview@4.1.2)(esbuild@0.27.5)(jiti@2.6.1)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.8))(msw@2.10.2(@types/node@24.10.0)(tsover@5.9.11))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.3):
+ vitest@4.0.18(@types/node@24.10.0)(@vitest/browser-preview@4.1.2(msw@2.10.2(@types/node@24.10.0)(tsover@5.9.11))(vite@8.0.2(@types/node@24.10.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.3))(vitest@4.1.2))(esbuild@0.27.5)(jiti@2.6.1)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.8))(msw@2.10.2(@types/node@24.10.0)(tsover@5.9.11))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.3):
dependencies:
'@vitest/expect': 4.0.18
'@vitest/mocker': 4.0.18(msw@2.10.2(@types/node@24.10.0)(tsover@5.9.11))(vite@8.0.2(@types/node@24.10.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.3))
From 9b70f146fa07369020a144bf626b084cc714ff8b Mon Sep 17 00:00:00 2001
From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com>
Date: Fri, 3 Apr 2026 12:51:49 +0200
Subject: [PATCH 07/12] Update readme
---
packages/eslint-plugin-internal/README.md | 48 +----------------------
1 file changed, 2 insertions(+), 46 deletions(-)
diff --git a/packages/eslint-plugin-internal/README.md b/packages/eslint-plugin-internal/README.md
index fae1227e74..4381d343a0 100644
--- a/packages/eslint-plugin-internal/README.md
+++ b/packages/eslint-plugin-internal/README.md
@@ -1,51 +1,7 @@
-# eslint-plugin-typegpu
+# eslint-plugin-internal
-TypeGPU specific linting rules for ESLint.
-
-[Docs](https://docs.swmansion.com/TypeGPU/tooling/eslint-plugin-typegpu/) -- [GitHub](https://github.com/software-mansion/TypeGPU/tree/main/packages/eslint-plugin) -- [npm](https://www.npmjs.com/package/eslint-plugin-typegpu)
+Internal ESLint rules used by this repository.
-
-## Installation
-
-`npm add -D eslint-plugin-typegpu`
-
-After installing, the plugin needs to be configured.
-
-## Configuration
-
-Configuration depends on the linter used.
-
-In eslint, either define the used rules manually, or use one of the configs provided by the plugin.
-
-```ts
-import { defineConfig } from "eslint/config";
-import typegpu from "eslint-plugin-typegpu";
-
-export default defineConfig([
-// other configs
- typegpu.configs.recommended,
-]);
-```
-
-`eslint-plugin-typegpu` provides two configs: `all` (enabled on all rules) and `recommended`.
-
-## List of supported rules
-
-
-
-🚨 Configurations enabled in.\
-⚠️ Configurations set to warn in.\
-⭐ Set in the `recommended` configuration.
-
-| Name | Description | 🚨 | ⚠️ |
-| :--------------------------------------------------------------------- | :------------------------------------------------------------------------------------------ | :- | :- |
-| [no-integer-division](docs/rules/no-integer-division.md) | Disallow division incorporating numbers wrapped in 'u32' and 'i32' | | ⭐ |
-| [no-invalid-assignment](docs/rules/no-invalid-assignment.md) | Disallow assignments that will generate invalid WGSL | ⭐ | |
-| [no-math](docs/rules/no-math.md) | Disallow usage of JavaScript 'Math' methods inside 'use gpu' functions | | ⭐ |
-| [no-uninitialized-variables](docs/rules/no-uninitialized-variables.md) | Disallow variable declarations without initializers inside 'use gpu' functions | ⭐ | |
-| [no-unwrapped-objects](docs/rules/no-unwrapped-objects.md) | Disallow unwrapped Plain Old JavaScript Objects inside 'use gpu' functions (except returns) | ⭐ | |
-
-
From 3d5d4202efecef99f0d432e7c9b2b08c77d87832 Mon Sep 17 00:00:00 2001
From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com>
Date: Fri, 3 Apr 2026 12:55:48 +0200
Subject: [PATCH 08/12] Make tests more portable
---
.../tests/rules/noLongImports.test.ts | 9 +++------
.../tests/rules/noUselessPathSegments.test.ts | 3 ++-
2 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/packages/eslint-plugin-internal/tests/rules/noLongImports.test.ts b/packages/eslint-plugin-internal/tests/rules/noLongImports.test.ts
index 69c89e7be8..5ed1e99489 100644
--- a/packages/eslint-plugin-internal/tests/rules/noLongImports.test.ts
+++ b/packages/eslint-plugin-internal/tests/rules/noLongImports.test.ts
@@ -2,19 +2,16 @@ import { describe } from 'vitest';
import { ruleTester } from '../utils/ruleTester.ts';
import { noLongImports } from '../../src/rules/noLongImports.ts';
-const filename = '/Users/me/typegpu-monorepo/packages/typegpu/tests/buffer.test.ts';
-
describe('noLongImports', () => {
ruleTester.run('noLongImports', noLongImports, {
valid: [
- { code: "import item from './file.ts';", filename },
- { code: "import item from '../file.ts';", filename },
- { code: "import item from '../../common/file.ts';", filename },
+ { code: "import item from './file.ts';" },
+ { code: "import item from '../file.ts';" },
+ { code: "import item from '../../common/file.ts';" },
],
invalid: [
{
code: "import item from '../../file.ts';",
- filename,
errors: [
{
messageId: 'unexpected',
diff --git a/packages/eslint-plugin-internal/tests/rules/noUselessPathSegments.test.ts b/packages/eslint-plugin-internal/tests/rules/noUselessPathSegments.test.ts
index 1b539ca78f..db67522d87 100644
--- a/packages/eslint-plugin-internal/tests/rules/noUselessPathSegments.test.ts
+++ b/packages/eslint-plugin-internal/tests/rules/noUselessPathSegments.test.ts
@@ -1,8 +1,9 @@
import { describe } from 'vitest';
import { ruleTester } from '../utils/ruleTester.ts';
import { noUselessPathSegments } from '../../src/rules/noUselessPathSegments.ts';
+import path from 'path';
-const filename = '/Users/me/typegpu-monorepo/packages/typegpu/tests/buffer.test.ts';
+const filename = path.join(process.cwd(), 'packages', 'typegpu', 'tests', 'buffer.test.ts');
describe('noUselessPathSegments', () => {
ruleTester.run('noUselessPathSegments', noUselessPathSegments, {
From c6f1c5742f217ddc608c8f9ecd3675ad354b008f Mon Sep 17 00:00:00 2001
From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com>
Date: Fri, 3 Apr 2026 12:57:38 +0200
Subject: [PATCH 09/12] Add windows compat
---
.../eslint-plugin-internal/src/rules/noUselessPathSegments.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/eslint-plugin-internal/src/rules/noUselessPathSegments.ts b/packages/eslint-plugin-internal/src/rules/noUselessPathSegments.ts
index 3268555b63..5e9717d007 100644
--- a/packages/eslint-plugin-internal/src/rules/noUselessPathSegments.ts
+++ b/packages/eslint-plugin-internal/src/rules/noUselessPathSegments.ts
@@ -27,7 +27,9 @@ export const noUselessPathSegments = createRule({
const filename = context.filename; // e.g. `/Users/me/typegpu-monorepo/packages/typegpu/tests/buffer.test.ts`
const dir = path.dirname(filename); // e.g. `/Users/me/typegpu-monorepo/packages/typegpu/tests`
const resolved = path.resolve(dir, importPath); // e.g. `/Users/me/typegpu-monorepo/packages/typegpu/src/data/index.ts`
- let simplified = path.relative(dir, resolved); // e.g. `../src/data/index.ts`, or `subfolder/helper.ts`
+ let simplified = path
+ .relative(dir, resolved) // e.g. `../src/data/index.ts`, or `subfolder/helper.ts`
+ .replaceAll('\\', '/'); // Windows compatibility
if (!simplified.startsWith('..')) {
simplified = `./${simplified}`;
From f375895614d28cd13699cc3da826740aac808056 Mon Sep 17 00:00:00 2001
From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com>
Date: Fri, 3 Apr 2026 13:38:39 +0200
Subject: [PATCH 10/12] Remove the fix tag
---
packages/eslint-plugin-internal/src/rules/noLongImports.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/packages/eslint-plugin-internal/src/rules/noLongImports.ts b/packages/eslint-plugin-internal/src/rules/noLongImports.ts
index 982fc1b5f1..936164240c 100644
--- a/packages/eslint-plugin-internal/src/rules/noLongImports.ts
+++ b/packages/eslint-plugin-internal/src/rules/noLongImports.ts
@@ -4,7 +4,6 @@ export const noLongImports = createRule({
name: 'no-long-imports',
meta: {
type: 'suggestion',
- fixable: 'code',
docs: {
description: 'Disallow long import paths (to be used in TypeGPU examples), except common.',
},
@@ -20,7 +19,7 @@ export const noLongImports = createRule({
return {
ImportDeclaration(node) {
const importPath = node.source.value;
- if (importPath.startsWith('../../') && !importPath.startsWith('../../common')) {
+ if (importPath.startsWith('../../') && !importPath.startsWith('../../common/')) {
context.report({
node,
messageId: 'unexpected',
From 4c4a9159ccce457fbacefa72b2002746e406cabf Mon Sep 17 00:00:00 2001
From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com>
Date: Fri, 3 Apr 2026 13:45:16 +0200
Subject: [PATCH 11/12] Simplify package.json
---
packages/eslint-plugin-internal/package.json | 19 +------------------
packages/eslint-plugin-internal/tsconfig.json | 2 +-
.../eslint-plugin-internal/tsdown.config.ts | 9 ---------
pnpm-lock.yaml | 6 ------
4 files changed, 2 insertions(+), 34 deletions(-)
delete mode 100644 packages/eslint-plugin-internal/tsdown.config.ts
diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json
index 53c342f4f9..b7cadb80a9 100644
--- a/packages/eslint-plugin-internal/package.json
+++ b/packages/eslint-plugin-internal/package.json
@@ -1,23 +1,9 @@
{
"name": "eslint-plugin-internal",
- "version": "0.10.0",
"private": true,
- "description": "Ruleset for working on TypeGPU monorepo",
"license": "MIT",
- "repository": {
- "type": "git",
- "url": "git+https://github.com/software-mansion/TypeGPU.git#main",
- "directory": "packages/eslint-plugin-internal"
- },
"type": "module",
- "sideEffects": false,
"main": "./src/index.ts",
- "exports": {
- ".": {
- "types": "./src/index.ts",
- "import": "./src/index.ts"
- }
- },
"scripts": {
"test:types": "pnpm tsc --p ./tsconfig.json --noEmit",
"test": "vitest"
@@ -29,13 +15,10 @@
"@types/node": "catalog:types",
"@typescript-eslint/rule-tester": "^8.57.2",
"eslint": "^9.39.2",
- "eslint-doc-generator": "^3.3.2",
- "tsdown": "^0.20.3",
"typescript": "^5.9.3",
"vitest": "^4.0.17"
},
"peerDependencies": {
"eslint": "^9.0.0"
- },
- "packageManager": "pnpm@10.15.1"
+ }
}
diff --git a/packages/eslint-plugin-internal/tsconfig.json b/packages/eslint-plugin-internal/tsconfig.json
index 3a544d3dd8..88024df5ec 100644
--- a/packages/eslint-plugin-internal/tsconfig.json
+++ b/packages/eslint-plugin-internal/tsconfig.json
@@ -4,5 +4,5 @@
"types": ["node"]
},
"include": ["src/**/*", "tests/**/*"],
- "exclude": ["node_modules", "dist"]
+ "exclude": ["node_modules"]
}
diff --git a/packages/eslint-plugin-internal/tsdown.config.ts b/packages/eslint-plugin-internal/tsdown.config.ts
deleted file mode 100644
index 3ec1155cc3..0000000000
--- a/packages/eslint-plugin-internal/tsdown.config.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { defineConfig } from 'tsdown';
-
-export default defineConfig({
- entry: ['src/index.ts'],
- format: ['esm'],
- dts: true,
- clean: true,
- sourcemap: true,
-});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cb799d73dc..d6cc58d7e8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -448,12 +448,6 @@ importers:
eslint:
specifier: ^9.39.2
version: 9.39.2(jiti@2.6.1)
- eslint-doc-generator:
- specifier: ^3.3.2
- version: 3.3.2(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.0)(tsover@5.9.11)
- tsdown:
- specifier: ^0.20.3
- version: 0.20.3(tsover@5.9.11)
typescript:
specifier: npm:tsover@^5.9.11
version: tsover@5.9.11
From 8baf753096e32cbe2cccb03bbf2cfb839876ca6b Mon Sep 17 00:00:00 2001
From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com>
Date: Fri, 3 Apr 2026 13:48:38 +0200
Subject: [PATCH 12/12] Readd version to package.json
---
packages/eslint-plugin-internal/package.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json
index b7cadb80a9..e2b1dc379a 100644
--- a/packages/eslint-plugin-internal/package.json
+++ b/packages/eslint-plugin-internal/package.json
@@ -1,5 +1,6 @@
{
"name": "eslint-plugin-internal",
+ "version": "0.10.0",
"private": true,
"license": "MIT",
"type": "module",