mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 20:27:15 +01:00
ts_config: project option, entry-point barrel and className carve-outs
Surfaced by making ts_core and web_ui the first consumers:
- base({ project }) uses an explicit tsconfig list instead of the project
service. Both packages exclude tests from tsconfig.json so they never land
in dist/, and widen the include in tsconfig.lint.json for lint only; the
project service would have reported every test file as "not found".
- unicorn/no-barrel-files is off for src/index.ts: a package entry point IS
a barrel, that is its job. Stays on everywhere else.
- unicorn/no-keyword-prefix is off in the React layer: `className` is
React's prop name and fires on every styled element.
- pnpm-workspace.yaml: allowBuilds for unrs-resolver set to false explicitly
(pnpm 12 had written a placeholder); the resolver's prebuilt binding is
used, no install script runs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnAYyNH1Sm4iJNidfYbrZJ
This commit is contained in:
parent
128ede438e
commit
0f9699d4f3
@ -51,9 +51,16 @@ export { DEFAULT_IGNORES } from "./ignores.js";
|
||||
* @param {boolean} [options.aliasOnly] Ban `../` parent-relative imports so
|
||||
* every cross-directory import goes through the repo's `@/` alias. Default
|
||||
* true; a repo with no alias configured turns it off with a reason.
|
||||
* @param {string[]} [options.project] tsconfig paths for the type-aware
|
||||
* rules, relative to tsconfigRootDir. Default is the project service
|
||||
* (nearest tsconfig.json per file). A package whose tsconfig.json excludes
|
||||
* its tests so they never land in dist/ passes its lint tsconfig here.
|
||||
*/
|
||||
export function base({ aliasOnly = true, ignores = [], tsconfigRootDir }) {
|
||||
export function base({ aliasOnly = true, ignores = [], project, tsconfigRootDir }) {
|
||||
requireNonEmptyString(tsconfigRootDir, "base() tsconfigRootDir");
|
||||
const parserOptions = project
|
||||
? { project, tsconfigRootDir }
|
||||
: { projectService: true, tsconfigRootDir };
|
||||
return tseslint.config(
|
||||
{ ignores: [...DEFAULT_IGNORES, ...ignores] },
|
||||
{ linterOptions: { reportUnusedDisableDirectives: "error" } },
|
||||
@ -67,9 +74,7 @@ export function base({ aliasOnly = true, ignores = [], tsconfigRootDir }) {
|
||||
|
||||
{
|
||||
files: TS_FILES,
|
||||
languageOptions: {
|
||||
parserOptions: { projectService: true, tsconfigRootDir },
|
||||
},
|
||||
languageOptions: { parserOptions },
|
||||
rules: {
|
||||
// `type X = {}` and `interface X {}` are interchangeable for the
|
||||
// shapes this codebase writes; picking one keeps diffs quiet.
|
||||
|
||||
@ -24,6 +24,7 @@ export { vitestConfig } from "./vitest.js";
|
||||
* consumer's eslint.config.js.
|
||||
* @param {string[]} [options.ignores]
|
||||
* @param {boolean} [options.aliasOnly]
|
||||
* @param {string[]} [options.project] See base().
|
||||
* @param {boolean} [options.react] Stack the React layer.
|
||||
* @param {boolean} [options.vitest] Stack the vitest rules (default true).
|
||||
* @param {boolean} [options.playwright] Stack the Playwright rules.
|
||||
@ -38,12 +39,13 @@ export function defineConfig({
|
||||
ignores,
|
||||
overrides = [],
|
||||
playwright = false,
|
||||
project,
|
||||
react: withReact = false,
|
||||
tsconfigRootDir,
|
||||
vitest = true,
|
||||
}) {
|
||||
return [
|
||||
...base({ aliasOnly, ignores, tsconfigRootDir }),
|
||||
...base({ aliasOnly, ignores, project, tsconfigRootDir }),
|
||||
...(withReact ? react : []),
|
||||
...(vitest ? vitestConfig : []),
|
||||
...(playwright ? playwrightConfig : []),
|
||||
|
||||
@ -11,6 +11,13 @@
|
||||
|
||||
/** @type {import("eslint").Linter.Config[]} */
|
||||
export const overrides = [
|
||||
{
|
||||
// A package's entry point IS a barrel: its whole job is to name the
|
||||
// public API. The rule stays on everywhere else, where a barrel is how
|
||||
// `import { x } from "@/utils"` ends up pulling in forty modules.
|
||||
files: ["src/index.{ts,tsx}", "index.{ts,tsx}"],
|
||||
rules: { "unicorn/no-barrel-files": "off" },
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
// Directly contradicts unicorn/consistent-class-member-order: one wants
|
||||
|
||||
8
ts_config/eslint/react.js
vendored
8
ts_config/eslint/react.js
vendored
@ -45,4 +45,12 @@ export const react = [
|
||||
files: ["**/*.tsx", "**/*.jsx"],
|
||||
...reactRefresh.configs.vite,
|
||||
},
|
||||
{
|
||||
files: TS_FILES,
|
||||
rules: {
|
||||
// `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",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
allowBuilds:
|
||||
unrs-resolver: set this to true or false
|
||||
unrs-resolver: false
|
||||
minimumReleaseAgeExclude:
|
||||
- eslint-plugin-unicorn@75.0.0
|
||||
|
||||
@ -36,6 +36,17 @@ describe("base()", () => {
|
||||
expect(entry?.languageOptions.parserOptions.tsconfigRootDir).toBe(ROOT);
|
||||
});
|
||||
|
||||
it("uses an explicit project list instead of the project service when given", () => {
|
||||
const entry = base({ project: ["./tsconfig.lint.json"], tsconfigRootDir: ROOT }).find(
|
||||
(candidate) => candidate.languageOptions?.parserOptions?.project,
|
||||
);
|
||||
expect(entry.languageOptions.parserOptions).toEqual({
|
||||
project: ["./tsconfig.lint.json"],
|
||||
tsconfigRootDir: ROOT,
|
||||
});
|
||||
expect(entry.languageOptions.parserOptions.projectService).toBeUndefined();
|
||||
});
|
||||
|
||||
it("drops the alias-only import ban when asked", () => {
|
||||
expect(ruleIds(base({ tsconfigRootDir: ROOT }))).toContain("no-restricted-imports");
|
||||
expect(ruleIds(base({ aliasOnly: false, tsconfigRootDir: ROOT }))).not.toContain(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user