Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c9f0366
Duplicate eslint plugin
aleksanderkatan Apr 2, 2026
3c52c10
Strip the plugin of unused code, enable plugin in config
aleksanderkatan Apr 3, 2026
2916f67
Implement noUselessPathSegments
aleksanderkatan Apr 3, 2026
f2b4ca9
Implement noLongImports rule
aleksanderkatan Apr 3, 2026
64dc8ad
nr fix
aleksanderkatan Apr 3, 2026
93d0dda
Merge remote-tracking branch 'origin/main' into dx/lint-rule-for-imports
aleksanderkatan Apr 3, 2026
d4f5c1c
Fix lockfile
aleksanderkatan Apr 3, 2026
9b70f14
Update readme
aleksanderkatan Apr 3, 2026
3d5d420
Make tests more portable
aleksanderkatan Apr 3, 2026
c6f1c57
Add windows compat
aleksanderkatan Apr 3, 2026
f375895
Remove the fix tag
aleksanderkatan Apr 3, 2026
4c4a915
Simplify package.json
aleksanderkatan Apr 3, 2026
8baf753
Readd version to package.json
aleksanderkatan Apr 3, 2026
45956ad
Merge remote-tracking branch 'origin/main' into dx/lint-rule-for-imports
aleksanderkatan Apr 28, 2026
bd62170
nr fix
aleksanderkatan Apr 28, 2026
0092351
bump version
aleksanderkatan Apr 28, 2026
05e8514
Merge remote-tracking branch 'origin/main' into dx/lint-rule-for-imports
aleksanderkatan May 11, 2026
6edb951
nr fix
aleksanderkatan May 11, 2026
1679918
Make dependency versions catalog
aleksanderkatan May 11, 2026
f930784
Add a valid test for "
aleksanderkatan May 11, 2026
3750c24
Remove noLongImports.ts rule
aleksanderkatan May 11, 2026
59a922d
Merge branch 'main' into dx/lint-rule-for-imports
aleksanderkatan May 13, 2026
9cb6f6e
Merge branch 'main' into dx/lint-rule-for-imports
aleksanderkatan May 15, 2026
ecc418c
Merge branch 'main' into dx/lint-rule-for-imports
aleksanderkatan May 18, 2026
f087658
Merge branch 'main' into dx/lint-rule-for-imports
aleksanderkatan May 20, 2026
041bdef
Merge branch 'main' into dx/lint-rule-for-imports
aleksanderkatan May 21, 2026
44064ac
Merge branch 'main' into dx/lint-rule-for-imports
aleksanderkatan May 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion oxlint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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-useless-path-segments': 'error',
},
ignorePatterns: ['**/*.astro', '**/*.mjs'],
overrides: [
Expand All @@ -45,6 +46,10 @@ export default defineConfig({
...(eslintPlugin.configs.recommended.rules as Record<string, 'error' | 'warn' | 'off'>),
},
},
{
files: ['apps/typegpu-docs/src/examples/**/*.ts'],
rules: { 'eslint-plugin-internal/no-long-imports': 'error' },
},
],
env: {
builtin: true,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 7 additions & 0 deletions packages/eslint-plugin-internal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div align="center">

# eslint-plugin-internal

Internal ESLint rules used by this repository.

</div>
25 changes: 25 additions & 0 deletions packages/eslint-plugin-internal/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "eslint-plugin-internal",
"version": "0.10.0",
"private": true,
"license": "MIT",
"type": "module",
"main": "./src/index.ts",
"scripts": {
"test:types": "pnpm tsc --p ./tsconfig.json --noEmit",
"test": "vitest"
},
"dependencies": {
"@typescript-eslint/utils": "^8.57.2"
},
"devDependencies": {
"@types/node": "catalog:types",
"@typescript-eslint/rule-tester": "^8.57.2",
"eslint": "^9.39.2",
"typescript": "^5.9.3",
Comment thread
aleksanderkatan marked this conversation as resolved.
Outdated
"vitest": "^4.0.17"
Comment thread
aleksanderkatan marked this conversation as resolved.
Outdated
},
"peerDependencies": {
"eslint": "^9.0.0"
}
}
17 changes: 17 additions & 0 deletions packages/eslint-plugin-internal/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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: {
name: pkg.name,
version: pkg.version,
},
rules: {
'no-useless-path-segments': noUselessPathSegments,
'no-long-imports': noLongImports,
},
} satisfies TSESLint.FlatConfig.Plugin;

export default plugin;
5 changes: 5 additions & 0 deletions packages/eslint-plugin-internal/src/ruleCreator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ESLintUtils } from '@typescript-eslint/utils';

export const createRule = ESLintUtils.RuleCreator(
() => `https://docs.swmansion.com/TypeGPU/getting-started/`,
);
32 changes: 32 additions & 0 deletions packages/eslint-plugin-internal/src/rules/noLongImports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { createRule } from '../ruleCreator.ts';

export const noLongImports = createRule({
name: 'no-long-imports',
meta: {
type: 'suggestion',
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/')) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this introduce a pretty high chance of false positives?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only enable this rule on example files, where this will not work due to stackblitz pathing requirements

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not ../?

context.report({
node,
messageId: 'unexpected',
data: { path: importPath },
});
}
},
};
},
});
52 changes: 52 additions & 0 deletions packages/eslint-plugin-internal/src/rules/noUselessPathSegments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
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('.')) {
Comment thread
cieplypolar marked this conversation as resolved.
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`
.replaceAll('\\', '/'); // Windows compatibility

if (!simplified.startsWith('..')) {
simplified = `./${simplified}`;
}
Comment thread
aleksanderkatan marked this conversation as resolved.

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}`);
},
});
}
},
};
},
});
24 changes: 24 additions & 0 deletions packages/eslint-plugin-internal/tests/rules/noLongImports.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe } from 'vitest';
import { ruleTester } from '../utils/ruleTester.ts';
import { noLongImports } from '../../src/rules/noLongImports.ts';

describe('noLongImports', () => {
ruleTester.run('noLongImports', noLongImports, {
valid: [
{ 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';",
errors: [
{
messageId: 'unexpected',
data: { path: '../../file.ts' },
},
],
},
],
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { describe } from 'vitest';
import { ruleTester } from '../utils/ruleTester.ts';
import { noUselessPathSegments } from '../../src/rules/noUselessPathSegments.ts';
import path from 'path';

const filename = path.join(process.cwd(), 'packages', 'typegpu', 'tests', 'buffer.test.ts');
Comment thread
aleksanderkatan marked this conversation as resolved.

describe('noUselessPathSegments', () => {
ruleTester.run('noUselessPathSegments', noUselessPathSegments, {
valid: [
Comment thread
cieplypolar marked this conversation as resolved.
{ 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';",
},
],
});
});
10 changes: 10 additions & 0 deletions packages/eslint-plugin-internal/tests/utils/ruleTester.ts
Original file line number Diff line number Diff line change
@@ -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();
8 changes: 8 additions & 0 deletions packages/eslint-plugin-internal/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": ["node"]
},
"include": ["src/**/*", "tests/**/*"],
"exclude": ["node_modules"]
}
8 changes: 8 additions & 0 deletions packages/eslint-plugin-internal/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
include: ['tests/**/*.test.ts'],
environment: 'node',
},
});
2 changes: 1 addition & 1 deletion packages/typegpu/src/core/pipeline/computePipeline.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
4 changes: 2 additions & 2 deletions packages/typegpu/src/core/pipeline/renderPipeline.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
2 changes: 1 addition & 1 deletion packages/typegpu/src/core/querySet/querySet.ts
Original file line number Diff line number Diff line change
@@ -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<T extends GPUQueryType> extends TgpuNamable {
Expand Down
6 changes: 1 addition & 5 deletions packages/typegpu/src/core/root/init.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
4 changes: 2 additions & 2 deletions packages/typegpu/src/core/root/rootTypes.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion packages/typegpu/src/core/slot/slotTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
10 changes: 5 additions & 5 deletions packages/typegpu/src/core/unroll/tgpuUnroll.ts
Original file line number Diff line number Diff line change
@@ -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';

/**
Expand Down
6 changes: 3 additions & 3 deletions packages/typegpu/src/tgsl/generationHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
Loading
Loading