Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion apps/website/app/(home)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { HomeLayout } from "fumadocs-ui/layouts/home";
import type { LinkItemType } from "fumadocs-ui/layouts/shared";
import type { ReactNode } from "react";

import { baseOptions } from "#/app/layout.config";
import { baseOptions } from "@/app/layout.config";

const links = [
{
Expand Down
2 changes: 1 addition & 1 deletion apps/website/app/(home)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Card, Cards } from "fumadocs-ui/components/card";
import { CircleDotDashed, Gauge, Sliders, Zap } from "lucide-react";
import { Link } from "next-view-transitions";

import { ESLintReact } from "#/components/ESLintReact";
import { ESLintReact } from "@/components/ESLintReact";

const features = [
["Modern", "First-class support for TypeScript, React 19, and more.", Zap],
Expand Down
2 changes: 1 addition & 1 deletion apps/website/app/api/search/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { source } from "#/lib/source";
import { source } from "@/lib/source";
import { createFromSource } from "fumadocs-core/search/server";

export const { GET } = createFromSource(source, {
Expand Down
6 changes: 3 additions & 3 deletions apps/website/app/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getMDXComponents } from "#/components/mdx";
import { gitConfig } from "#/lib/shared";
import { getPageImage, getPageMarkdownUrl, source } from "#/lib/source";
import { getMDXComponents } from "@/components/mdx";
import { gitConfig } from "@/lib/shared";
import { getPageImage, getPageMarkdownUrl, source } from "@/lib/source";
import {
DocsBody,
DocsDescription,
Expand Down
2 changes: 1 addition & 1 deletion apps/website/app/docs/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { source } from "#/lib/source";
import { source } from "@/lib/source";
import { DocsLayout } from "fumadocs-ui/layouts/docs";
import { baseOptions } from "../layout.config";

Expand Down
2 changes: 1 addition & 1 deletion apps/website/app/layout.config.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
import Image from "next/image";

import logo from "#/assets/logo.svg";
import logo from "@/assets/logo.svg";

/**
* Shared layout configurations
Expand Down
6 changes: 3 additions & 3 deletions apps/website/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { baseUrl } from "#/lib/metadata";
import { baseUrl } from "@/lib/metadata";
import { Banner } from "fumadocs-ui/components/banner";
import { RootProvider } from "fumadocs-ui/provider/next";
import { Metadata } from "next";
import { Link, ViewTransitions } from "next-view-transitions";

import "#/app/app.css";
import "#/app/app.override.css";
import "@/app/app.css";
import "@/app/app.override.css";

const themeOptions = {
enabled: true,
Expand Down
2 changes: 1 addition & 1 deletion apps/website/app/llms-full.txt/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getLLMText, source } from "#/lib/source";
import { getLLMText, source } from "@/lib/source";

export const revalidate = false;

Expand Down
2 changes: 1 addition & 1 deletion apps/website/app/llms.mdx/docs/[[...slug]]/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getLLMText, getPageMarkdownUrl, source } from "#/lib/source";
import { getLLMText, getPageMarkdownUrl, source } from "@/lib/source";
import { notFound } from "next/navigation";

export const revalidate = false;
Expand Down
2 changes: 1 addition & 1 deletion apps/website/app/llms.txt/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { source } from "#/lib/source";
import { source } from "@/lib/source";
import { llms } from "fumadocs-core/source";

export const revalidate = false;
Expand Down
2 changes: 1 addition & 1 deletion apps/website/app/og/docs/[...slug]/route.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getPageImage, source } from "#/lib/source";
import { getPageImage, source } from "@/lib/source";
import { ImageResponse } from "@takumi-rs/image-response";
import { generate as DefaultImage } from "fumadocs-ui/og/takumi";
import { notFound } from "next/navigation";
Expand Down
2 changes: 1 addition & 1 deletion apps/website/components/BskyPost.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cn } from "#/lib/cn";
import { cn } from "@/lib/cn";
import { Post } from "bsky-react-post";
import "bsky-react-post/theme.css";

Expand Down
2 changes: 1 addition & 1 deletion apps/website/components/ESLintReact.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import logo from "#/assets/logo.svg";
import logo from "@/assets/logo.svg";
import Image, { type StaticImageData } from "next/image";

export function ESLintReact() {
Expand Down
2 changes: 1 addition & 1 deletion apps/website/components/GitHubRepo.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cn } from "#/lib/cn";
import { cn } from "@/lib/cn";
import { GithubInfo } from "fumadocs-ui/components/github-info";

export function GitHubRepo({ className, token, ...rest }: Parameters<typeof GithubInfo>[0]) {
Expand Down
4 changes: 2 additions & 2 deletions apps/website/components/GitHubRepoGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GitHubRepo } from "#/components/GitHubRepo";
import { cn } from "#/lib/cn";
import { GitHubRepo } from "@/components/GitHubRepo";
import { cn } from "@/lib/cn";

interface GitHubRepoGridProps {
className?: string;
Expand Down
2 changes: 1 addition & 1 deletion apps/website/components/ui/Callout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cn } from "#/lib/cn";
import { cn } from "@/lib/cn";
import type { Pretty } from "@local/eff";
import { Callout as FDCallout } from "fumadocs-ui/components/callout";
import type React from "react";
Expand Down
4 changes: 2 additions & 2 deletions apps/website/content/docs/community.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ title: Community
description: "Discover the growing community around ESLint React"
---

import { BskyPost } from "#/components/BskyPost";
import { GitHubRepoGrid } from "#/components/GitHubRepoGrid";
import { BskyPost } from "@/components/BskyPost";
import { GitHubRepoGrid } from "@/components/GitHubRepoGrid";
import { projects, presets } from "./community.tsx";

## OSS Projects Using ESLint React
Expand Down
2 changes: 1 addition & 1 deletion apps/website/content/docs/diagrams.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MermaidDiagram } from "#/components/ui/MermaidDiagram";
import { MermaidDiagram } from "@/components/ui/MermaidDiagram";
import mermaid from "dedent";

const functionComponentCollectorSequence = mermaid`
Expand Down
2 changes: 1 addition & 1 deletion apps/website/content/docs/packages/kit.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: ESLint React's toolkit for building custom React rules with JavaScr
---

import { AutoClick } from "./kit"
import { BskyPost } from "#/components/BskyPost";
import { BskyPost } from "@/components/BskyPost";

<Callout type="warning">
This module is currently in **beta**. APIs may change in future releases.
Expand Down
2 changes: 1 addition & 1 deletion apps/website/proxy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { docsContentRoute, docsRoute } from "#/lib/shared";
import { docsContentRoute, docsRoute } from "@/lib/shared";
import { isMarkdownPreferred, rewritePath } from "fumadocs-core/negotiation";
import { NextRequest, NextResponse } from "next/server";

Expand Down
4 changes: 2 additions & 2 deletions apps/website/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
"incremental": true,
"erasableSyntaxOnly": true,
"paths": {
"#": ["."],
"#/*": ["./*"],
"@": ["."],
"@/*": ["./*"],
"collections/*": ["./.source/*"]
},
"plugins": [
Expand Down
157 changes: 157 additions & 0 deletions docs/import-paths.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Import Paths Convention

This monorepo uses TypeScript `paths` to provide clean, stable import aliases. Instead of relying on brittle relative paths like `../../utils/create-rule`, use the aliases defined in each package's `tsconfig.json`.

## Table of Contents

- [Overview](#overview)
- [`@` — Package Source Root](#-package-source-root)
- [`#` — Workspace Root](#-workspace-root)
- [Configuration](#configuration)
- [Examples](#examples)
- [FAQ](#faq)

## Overview

| Alias | Target | Purpose |
| ----- | --------- | ---------------------------------------------------------------------- |
| `@` | `./src` | Import from the current package's source directory. |
| `@/*` | `./src/*` | Import a sub-module from the current package's source directory. |
| `#` | `../..` | Import from the workspace root (shared utilities, test helpers, etc.). |
| `#/*` | `../../*` | Import a sub-module from the workspace root. |

## `@` — Package Source Root

Use `@` whenever you need to reference a module inside the **current package's `src/` directory**.

This eliminates the need to count `../` segments and makes refactorings (moving files between directories) much safer.

```ts
// ❌ Avoid — breaks when the file is moved
import { createRule } from "../../utils/create-rule";

// ✅ Preferred — stable regardless of file depth
import { createRule } from "@/utils/create-rule";
```

### Where it resolves

In every package under `packages/*` and `plugins/*`, `@` is mapped to that package's own `src/` folder:

```json
{
"compilerOptions": {
"paths": {
"@": ["./src"],
"@/*": ["./src/*"]
Comment thread
Rel1cx marked this conversation as resolved.
}
}
}
```

## `#` — Workspace Root

Use `#` when you need to reference a module at the **monorepo root** (e.g. shared test helpers in `test/`, build scripts, or workspace-wide types).

```ts
// ❌ Avoid — fragile and hard to read
import { ruleTester } from "../../../../../test";

// ✅ Preferred — always points to the workspace root
import { ruleTester } from "#/test";
```

### Where it resolves

In each package, `#` is mapped two levels up (`../..`) to reach the workspace root:

```json
{
"compilerOptions": {
"paths": {
"#": ["../.."],
"#/*": ["../../*"]
}
}
}
```

The workspace root `tsconfig.json` also defines `#` for root-level files:

```json
{
"compilerOptions": {
"paths": {
"#": ["."],
"#/*": ["./*"]
}
}
}
```

## Configuration

### Per-package `tsconfig.json`

Every plugin and package must declare both aliases in its local `tsconfig.json`:

```json
{
"extends": ["@local/configs/tsconfig.base.json"],
"compilerOptions": {
"paths": {
"@": ["./src"],
"#": ["../.."],
"@/*": ["./src/*"],
"#/*": ["../../*"]
}
},
"include": ["src"]
}
```

### Vitest support

Vitest is configured to resolve these aliases via `resolve.tsconfigPaths: true` in `vitest.config.ts`, so tests run with the same mappings as the TypeScript compiler.

## Examples

### Inside a plugin rule implementation

```ts
// plugins/eslint-plugin-react-x/src/rules/no-missing-key/no-missing-key.ts
import { createRule } from "@/utils/create-rule";
import { getSettings } from "@/utils/get-settings";
```

### Inside a plugin test file

```ts
// plugins/eslint-plugin-react-x/src/rules/no-missing-key/no-missing-key.spec.ts
import { ruleTester } from "#/test";
import { createRule } from "@/utils/create-rule";
```

### Inside a package source file

```ts
// packages/ast/src/compare.ts
import { isTypeExpression } from "@/check";
```

## FAQ

### Why not use `~` or `#` for package-local imports?

We follow the convention where `@` represents the **current package scope** (similar to many Vite / Next.js setups) and `#` represents the **workspace scope**. This keeps the mental model simple:

- `@/*` = "inside this package"
- `#/*` = "inside the whole repo"

### What about `packages/` that import each other?

Cross-package imports should still use the real package name (e.g. `@eslint-react/ast`, `@eslint-react/core`). `paths` aliases are only for intra-package and intra-workspace references that would otherwise require deep relative paths.

### Can I use these aliases in build outputs?

No — these aliases are for **source code only**. The bundler (`tsdown`) is configured to resolve and inline them during the build. Consumers of the published packages never see `@/` or `#/` imports.
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,16 @@
"lint:ts": "pnpm m run lint:ts",
"lint:website": "pnpm -F \"./apps/website\" run lint",
"prepare": "pnpm run build",
"rename:rule": "tsx ./scripts/rename-rule.ts",
"scaffold:rule": "tsx ./scripts/scaffold-rule.ts",
"rename:rule": "vite-node ./scripts/rename-rule.ts",
"scaffold:rule": "vite-node ./scripts/scaffold-rule.ts",
"sort:package-json": "pnpm m exec sort-package-json -q package.json && dprint fmt",
"test": "vitest run --logHeapUsage",
"update:all": "pnpm run update:version && pnpm run update:readme && pnpm run update:website",
"update:readme": "tsx ./scripts/update-readme.ts",
"update:version": "tsx ./scripts/update-version.ts",
"update:website": "tsx ./scripts/update-website.ts",
"verify:configs": "tsx ./scripts/verify-configs.ts",
"verify:rule-docs": "tsx ./scripts/verify-rule-docs.ts"
"update:readme": "vite-node ./scripts/update-readme.ts",
"update:version": "vite-node ./scripts/update-version.ts",
"update:website": "vite-node ./scripts/update-website.ts",
"verify:configs": "vite-node -c vitest.config.ts ./scripts/verify-configs.ts",
"verify:rule-docs": "vite-node -c vitest.config.ts ./scripts/verify-rule-docs.ts"
Comment thread
Rel1cx marked this conversation as resolved.
},
"devDependencies": {
"@effect/language-service": "^0.85.1",
Expand Down Expand Up @@ -94,12 +94,12 @@
"ts-pattern": "^5.9.0",
"tsdown": "^0.21.10",
"tsl": "^1.0.30",
"tsx": "^4.21.0",
"typedoc": "^0.28.19",
"typedoc-plugin-markdown": "^4.11.0",
"typedoc-plugin-mdn-links": "^5.1.1",
"typescript": "^6.0.3",
"typescript-eslint": "^8.59.1",
"vite-node": "^6.0.0",
"vitest": "^4.1.5"
},
"packageManager": "pnpm@10.33.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import tsx from "dedent";

import { ruleTester } from "../../../../../test";
import { stringify } from "../../utils/stringify";
import { ruleTester } from "#/test";
import { stringify } from "@/utils/stringify";
import rule, { RULE_NAME } from "./function-component";

ruleTester.run(RULE_NAME, rule, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as core from "@eslint-react/core";
import { type RuleContext, type RuleFeature, merge } from "@eslint-react/eslint";

import { createRule } from "../../utils/create-rule";
import { stringify } from "../../utils/stringify";
import { createRule } from "@/utils/create-rule";
import { stringify } from "@/utils/stringify";

export const RULE_NAME = "function-component";

Expand Down
4 changes: 2 additions & 2 deletions plugins/eslint-plugin-react-debug/src/rules/hook/hook.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import tsx from "dedent";

import { ruleTester } from "../../../../../test";
import { stringify } from "../../utils/stringify";
import { ruleTester } from "#/test";
import { stringify } from "@/utils/stringify";
import rule, { RULE_NAME } from "./hook";

ruleTester.run(RULE_NAME, rule, {
Expand Down
Loading
Loading