# Type Checking & Linting

URL: https://zerodev.ca/docs/medusa/developer-documentation/tooling

> Type-check the plain-JavaScript backend in place with checkJs and enforce the house style with ESLint.



The Medusa backend (the Kora framework in `src/medusa/`, the module loaders in `src/modules/`, and every module under `modules/`) is plain ESM JavaScript that runs directly through `node index.js` with no build step. It is still type-checked and linted in place: TypeScript verifies the JavaScript through `checkJs`, and ESLint enforces the correctness baseline plus the project house style. The web dashboard under `src/dashboard/` keeps its own TypeScript and ESLint configuration and is excluded from both backend tools.

## Commands

```bash
bun run typecheck
bun run typecheck:modules
bun run lint
bun run lint:fix
```

`typecheck` verifies the backend framework against `tsconfig.backend.json`. `typecheck:modules` verifies all modules in `modules/` against `tsconfig.module.json`. Neither command emits output. `lint` reports across the assembled checkout, including every module worktree present in `modules/`.

## Type Declarations

Type declarations reside in `src/types/` rather than alongside runtime source files:

* `src/types/medusa/`: Framework declarations and package augmentations (`index.d.ts`, `canvas.d.ts`, `packages.d.ts`).
* `src/types/modules/`: Module base classes and contracts (`index.d.ts`, `types.d.ts`).

`package.json` maps subpath imports with explicit `types` targets:

```json title="package.json"
{
  "imports": {
    "#medusa": {
      "types": "./src/types/medusa/index.d.ts",
      "default": "./src/medusa/index.js"
    },
    "#medusa/modules": {
      "types": "./src/types/modules/index.d.ts",
      "default": "./src/modules/index.js"
    }
  }
}
```

This structure gives module authors and editors complete type resolution and autocomplete for every base class and runtime service without building or transpiling.

## Type Checking

`tsconfig.backend.json` type-checks the JavaScript without compiling it. There is no transpile and no output directory; `node index.js` remains the only runtime.

```json title="tsconfig.backend.json"
{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "allowJs": true,
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "noImplicitAny": false,
    "strictNullChecks": false
  }
}
```

`NodeNext` resolution matches how Node runs the code: explicit `.js` import extensions and the `#medusa` subpath imports declared in `package.json`. `noImplicitAny` and `strictNullChecks` are relaxed so an untyped codebase does not report thousands of implicit-`any` and possibly-`null` errors on day one. Both are ratchets to tighten as annotation coverage grows.

### Adding Types with JSDoc

Types come from JSDoc annotations rather than TypeScript syntax, so files stay valid `.js`. A single `@param` clears every "property does not exist" error on a config-object parameter:

```js title="src/medusa/handlers/discord/components.js"
/**
 * @param {{ accent?: string, components?: any[], spoiler?: boolean, id?: number }} [options]
 */
container({ accent, components = [], spoiler = false, id } = {}) {
    const result = { type: 17, components: Array.isArray(components) ? components : [components] };
    if (accent !== undefined) result.accent_color = accent;
    if (typeof id === "number") result.id = id;
    return result;
}
```

<Callout type="info">
  JSDoc type blocks (`/** */`) are the one comment form the lint rules permit. They are type declarations, not prose, so they satisfy the no-comments rule while feeding `checkJs` its types.
</Callout>

## Linting

`eslint.config.js` at the repository root is a flat config that layers the house style over `@eslint/js` recommended. It ignores `src/dashboard/` and each module's `resources/dashboard/`, which lint under their own configuration.

### Enforced House Rules

| Rule                      | Enforcement                                                                                     |
| ------------------------- | ----------------------------------------------------------------------------------------------- |
| Optional chaining (`?.`)  | Banned via `no-restricted-syntax`. Use explicit checks.                                         |
| Nullish coalescing (`??`) | Banned via `no-restricted-syntax`. Use explicit checks.                                         |
| `else if`                 | Banned via `no-restricted-syntax`. Use early returns, guard clauses, lookup tables or `switch`. |
| Comments                  | Banned via `house/no-prose-comments`. Only JSDoc `/** */` blocks are allowed.                   |
| `var`                     | Banned; use `const` or `let`.                                                                   |
| Loose equality            | `eqeqeq` requires `===` and `!==`.                                                              |

`no-empty` permits empty `catch` blocks, and `no-unused-vars` ignores unused function parameters, since handler and event signatures keep positional parameters they do not always consume.

<Callout type="warn">
  The `||` value-fallback prohibition is not lint-enforced. There is no reliable way to distinguish a fallback (`const x = a || b`) from a boolean condition (`if (a || b)`) at the syntax level, so this rule is followed by convention rather than by the linter.
</Callout>

## Module Worktrees

Every module is an isolated git worktree on its own `module/<name>` branch, so the backend tools run against the assembled checkout where all module directories are present. Type and lint errors reported inside `modules/<name>/` belong to that module's branch and must be fixed and committed there, not on `main`. The root `tsconfig.backend.json`, `tsconfig.module.json` and `eslint.config.js` live on `main` only; module branches do not carry these files. Run `bun run typecheck:modules` to typecheck module worktrees against `tsconfig.module.json`.

## Dashboard

The dashboard in `src/dashboard/` is a Vite and React single-page app routed with React Router. It has no server code of its own: sign-in, page data (`/api/view/...`), dashboard actions (`/api/action/<name>`) and the `/api/m/<module>/...` proxy are served by the bot from `src/medusa/handlers/dashboard/`, which the backend `typecheck` and `lint` commands cover.

```bash
cd src/dashboard
bun x tsc --noEmit
bun x eslint .
```

The dashboard lint config applies the same bans: optional chaining, nullish coalescing, `else if` and prose comments. Directive comments such as `/* @vite-ignore */` and JSDoc blocks are allowed.

The root `tsconfig.json` type-checks module views under `modules/*/resources/dashboard/` against the dashboard's `@/` imports. `src/dashboard/toolchain.js` holds the tooling the bot loads at runtime: the Vite dev server used when `SERVER.DASHBOARD.DEVELOPER` is `true`, the esbuild compiler for module views and previews, and the Tailwind compiler for the classes they use.
