diff --git a/ts_config/README.md b/ts_config/README.md index c84120b..bcbb425 100644 --- a/ts_config/README.md +++ b/ts_config/README.md @@ -16,7 +16,7 @@ files in three different strictness tiers. ```jsonc // package.json "devDependencies": { - "@kuhyx/ts-config": "github:kuhyx/utils#ts_config-v0.1.1&path:/ts_config", + "@kuhyx/ts-config": "github:kuhyx/utils#ts_config-v0.1.2&path:/ts_config", "eslint": "10.10.0", "typescript": "6.0.3", "typescript-eslint": "8.70.0" @@ -51,8 +51,18 @@ export default defineConfig({ }); ``` -`knip.json`: `{ "extends": "@kuhyx/ts-config/knip" }`. `.jscpd.json`: copy -`jscpd.base.json` (jscpd has no extends). `stryker.config.mjs`: +Knip has no `extends` (its schema rejects the key), so `knip.ts` spreads the +base and adds the repo's entries: + +```ts +import type { KnipConfig } from "knip"; +import base from "@kuhyx/ts-config/knip" with { type: "json" }; +type KnipSettings = Exclude unknown>; +const config: KnipSettings = { ...(base as KnipSettings), entry: ["scripts/*.mjs"] }; +export default config; +``` + +`.jscpd.json`: copy `jscpd.base.json` (jscpd has no extends either). `stryker.config.mjs`: `import { strykerBase } from "@kuhyx/ts-config/stryker"; export default { ...strykerBase };` ## Adopting the alias rule in an existing repo diff --git a/ts_config/eslint/overrides.js b/ts_config/eslint/overrides.js index 70aad87..244dafd 100644 --- a/ts_config/eslint/overrides.js +++ b/ts_config/eslint/overrides.js @@ -55,6 +55,12 @@ export const overrides = [ // warns about is for non-ISO strings. "unicorn/prefer-temporal": "off", + // Same class as prefer-temporal: `Uint8Array.fromBase64` is undefined + // on the Node these repos run (24.x, checked 2026-09-16), so the rule's + // replacement for `Buffer.from(s, "base64")` throws at runtime. Revisit + // when the toolchain gate moves Node past a release that ships it. + "unicorn/prefer-uint8array-base64": "off", + // The domain vocabulary is abbreviated on purpose: `os`, `spdx`, `repo`, // `env`, `rng` are the names the source data and the maths use, and // expanding them would obscure the mapping. diff --git a/ts_config/eslint/react.js b/ts_config/eslint/react.js index 3c33966..753bc62 100644 --- a/ts_config/eslint/react.js +++ b/ts_config/eslint/react.js @@ -48,9 +48,26 @@ export const react = [ { files: TS_FILES, rules: { + // Same list as naming.js, minus `ref`: @eslint-react's + // naming-convention-ref-name wants every useRef() named `ref` or + // `*Ref`, and unicorn's `ref -> reference` replacement contradicts it + // on every one. `ref` is React's own word, like `props`. + "unicorn/name-replacements": ["error", { replacements: { props: false, ref: false } }], + // `className` is React's own prop name; in a React codebase the rule // fires on every styled element and cannot be satisfied. "unicorn/no-keyword-prefix": "off", }, }, + { + files: ["**/*.tsx", "**/*.jsx"], + rules: { + // `items.map((item) => (
  • …
  • ))` is the JSX idiom in every React + // codebase and what Prettier formats to; the rule would rewrite each + // one to `{ return (…); }`, and cannot even autofix the ones with + // multi-line text. It has no option to exempt JSX, so it is off for + // JSX files only -- .ts files keep it. + "unicorn/consistent-arrow-return-style": "off", + }, + }, ]; diff --git a/ts_config/knip.base.json b/ts_config/knip.base.json index e8c5ab2..479cf1d 100644 --- a/ts_config/knip.base.json +++ b/ts_config/knip.base.json @@ -1,7 +1,5 @@ { "$schema": "https://unpkg.com/knip@6/schema.json", - "ignore": ["**/dist/**", "**/coverage/**", "**/*.d.ts"], - "ignoreDependencies": ["@kuhyx/ts-config"], "ignoreExportsUsedInFile": true, "rules": { "binaries": "error", diff --git a/ts_config/package.json b/ts_config/package.json index 1e10850..3acd498 100644 --- a/ts_config/package.json +++ b/ts_config/package.json @@ -1,6 +1,6 @@ { "name": "@kuhyx/ts-config", - "version": "0.1.1", + "version": "0.1.2", "private": false, "type": "module", "description": "The one lint/TypeScript bar every kuhyx TypeScript repo extends: a strict tsconfig base, ESLint flat-config presets (base, react, vitest, playwright, layer boundaries, a fast editor subset), and Knip / jscpd / Stryker bases. Plain JS, no build step -- consumable straight from a git tag.", diff --git a/ts_config/test/expected.js b/ts_config/test/expected.js index e14f0d6..9e07680 100644 --- a/ts_config/test/expected.js +++ b/ts_config/test/expected.js @@ -26,4 +26,4 @@ export const EXPECTED = { }; /** Files that must lint clean: proves the presets do not fire on good code. */ -export const CLEAN = ["src/mocks/user.ts", "src/utils/helper.ts"]; +export const CLEAN = ["src/components/item-list.tsx", "src/mocks/user.ts", "src/utils/helper.ts"]; diff --git a/ts_config/test/fixture/src/components/item-list.tsx b/ts_config/test/fixture/src/components/item-list.tsx new file mode 100644 index 0000000..2a3d907 --- /dev/null +++ b/ts_config/test/fixture/src/components/item-list.tsx @@ -0,0 +1,21 @@ +// Clean: the JSX map idiom with an implicit multi-line return, which the +// React layer exempts from unicorn/consistent-arrow-return-style. +import { useRef } from "react"; + +export function ItemList({ items }: { readonly items: readonly string[] }): React.JSX.Element { + return ( + + ); +} + +/** Clean: a `*Ref` name satisfies both unicorn and @eslint-react. */ +export function Focused(): React.JSX.Element { + const inputRef = useRef(null); + return ; +}