Files
bot-agents/.agents/memory/project-lint-plugin.md
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

2.9 KiB

description
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.

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.