Files
bot-agents/.agents/memory/project-lint-plugin.md
T
Danny Kim 063eb09750 Replace NodeJS dict aliases with maps and named field reads
Co-authored-by: Danny Kim <temeddix@gmail.com>
Co-committed-by: Danny Kim <temeddix@gmail.com>
2026-10-20 05:01:49 +00:00

64 lines
2.9 KiB
Markdown

---
description: scripts/project-lint.ts is the repo-local plugin "project" holding the repo's rule list (typed DummyRuleMap) and repo-only rules; never published, no tests
---
Every repo has `scripts/project-lint.ts`, copied from
`cunarist/eslint-plugin-explicit-types` and extended. It is a repo-local
Oxlint plugin with `meta.name` `"project"`, loaded through `jsPlugins` in
`vite.config.ts`. It holds:
- **`configs.project.rules`**: the repo's whole rule list beyond the published
plugin configs: Deno parity rules, extras, per-rule options, and opt-outs,
built-in Oxlint ids included (`curly`, `id-match`, `import/no-cycle`, ...).
- **Custom rules**, enabled as `project/<rule>`. Every repo has
`no-disable-comments` and `no-asset-imports`. Since 2026-10-19 every repo
also has `prefer-ascii`, `no-nan` (a "no number" value is `null`, never
`NaN`), and the hugoalh `recommended` ports from
eslint-plugin-import-structure's own lint: `no-duplicate-typeofs`,
`no-empty-yield`, `no-unsafe-number`, `no-useless-continue`,
`no-duplicate-interfaces`, `no-duplicate-types`, `no-useless-type`, and
`no-modifier-private`. `no-duplicate-awaits` and `no-duplicate-voids` are
covered by `unicorn/no-unnecessary-await` and
`typescript/no-meaningless-void-operator`, and `no-enum` by explicit-types.
`scripts/` stays flat; don't add `scripts/rules/`. All rules live in the one
`scripts/project-lint.ts` (no file splitting), next to the plugin object and
the rule list.
```ts
import type { DummyRuleMap } from "vite-plus/lint";
const rules: DummyRuleMap = {
"id-match": ["error", "^_*[^_]*_*$|^[^a-z]*$", {/* ... */}],
curly: "error",
"project/no-disable-comments": "error",
};
const plugin = {
meta: { name: "project" },
rules: { "no-disable-comments": noDisableCommentsRule /* ... */ },
configs: { project: { rules } },
};
export default plugin;
```
## Why `DummyRuleMap` and an unannotated plugin (decided 2026-10-15)
- `DummyRuleMap` (re-exported by `vite-plus/lint`) is Oxlint's type for
`OxlintConfig.rules`. Despite the name it lists every built-in rule with its
options, so a typo in a rule id or option is a type error, not just a CI
failure. Without it, TypeScript widens `"error"` to `string`; `as const`
doesn't help because Oxlint wants mutable tuples.
- `@eslint/core`'s `Plugin` is too loose to read back: `configs` is optional,
any key may be missing, and a config may be an array, so
`projectLint.configs.project.rules` fails in `vite.config.ts` (and `!` is
banned). Optional chaining still leaves the array case.
- Oxlint's own `Plugin` type (`vite-plus/lint/plugins`) has no `configs`.
- Two hand-written interfaces worked but were dropped as noise; a plain
config object was rejected because custom rules need the plugin form anyway
and every repo should look the same.
`scripts/` is tooling, not production code: never packed or published, and
no tests. Its offs sit in the Node code entry of `projectOverrides`; see
[[lint-config]].