fix: resolve ESLint configuration and TypeScript errors across monorepo

- Add "type": "module" to @tpmjs/eslint-config package.json for ES module support
- Rename eslint.config.js to eslint.config.mjs in apps/web and packages/ui
- Update ESLint configs to ignore build directories (.next, dist, .turbo)
- Extend import/no-internal-modules allowlist for Next.js, testing libs, and React
- Fix unescaped quotes in playground page code elements
- Convert CardDescriptionProps from empty interface to type alias
- Disable jsx-a11y/no-autofocus rule in test files
- Change web app lint script from 'next lint' to 'eslint .'
- Fix TypeScript ref type inference errors in UI package tests by using non-null assertions
- Disable Biome noNonNullAssertion rule for test files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-11-26 13:25:30 +10:00
parent 27f867ff24
commit 54f2c1a2d1
23 changed files with 161 additions and 100 deletions

View file

@ -1,3 +0,0 @@
import reactConfig from '@tpmjs/eslint-config/react.js';
export default reactConfig;

View file

@ -0,0 +1,16 @@
import reactConfig from '@tpmjs/eslint-config/react.js';
export default [
{
ignores: [
'.next/**',
'.turbo/**',
'node_modules/**',
'*.config.js',
'*.config.ts',
'next-env.d.ts',
'eslint.config.mjs'
]
},
...reactConfig,
];

View file

@ -6,7 +6,7 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint": "eslint .",
"type-check": "tsc --noEmit",
"clean": "rm -rf .next .turbo"
},

View file

@ -167,7 +167,8 @@ export default function PlaygroundPage() {
<div className="space-y-4">
<h3 className="text-xl font-medium">Available Icons</h3>
<div className="grid grid-cols-4 md:grid-cols-6 lg:grid-cols-8 gap-6">
{([
{(
[
"check",
"x",
"chevronDown",
@ -176,7 +177,8 @@ export default function PlaygroundPage() {
"github",
"sun",
"moon",
] as const).map((icon) => (
] as const
).map((icon) => (
<div
key={icon}
className="flex flex-col items-center gap-2 p-3 rounded-lg hover:bg-surface transition-colors"
@ -463,28 +465,28 @@ export default function Example() {
<Container size="sm" padding="md">
<Card>
<CardContent className="p-4 text-center">
<code className="text-sm">size="sm"</code>
<code className="text-sm">size=&quot;sm&quot;</code>
</CardContent>
</Card>
</Container>
<Container size="md" padding="md">
<Card>
<CardContent className="p-4 text-center">
<code className="text-sm">size="md"</code>
<code className="text-sm">size=&quot;md&quot;</code>
</CardContent>
</Card>
</Container>
<Container size="lg" padding="md">
<Card>
<CardContent className="p-4 text-center">
<code className="text-sm">size="lg"</code>
<code className="text-sm">size=&quot;lg&quot;</code>
</CardContent>
</Card>
</Container>
<Container size="xl" padding="md">
<Card>
<CardContent className="p-4 text-center">
<code className="text-sm">size="xl"</code>
<code className="text-sm">size=&quot;xl&quot;</code>
</CardContent>
</Card>
</Container>

View file

@ -37,5 +37,17 @@
"trailingCommas": "es5",
"semicolons": "always"
}
},
"overrides": [
{
"include": ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx"],
"linter": {
"rules": {
"style": {
"noNonNullAssertion": "off"
}
}
}
}
]
}

View file

@ -1,6 +1,6 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import importPlugin from 'eslint-plugin-import';
import js from "@eslint/js";
import importPlugin from "eslint-plugin-import";
import tseslint from "typescript-eslint";
export default tseslint.config(
js.configs.recommended,
@ -11,22 +11,34 @@ export default tseslint.config(
},
rules: {
// Module boundaries
'import/no-internal-modules': ['error', {
"import/no-internal-modules": [
"error",
{
allow: [
'@tpmjs/ui/*/[!index]*',
'@tpmjs/utils/*',
'@tpmjs/types/*',
]
}],
'import/no-restricted-paths': ['error', {
zones: [
{ target: './packages', from: './apps' },
{ target: './packages/ui', from: './packages/utils' },
]
}],
'import/no-anonymous-default-export': 'error',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-explicit-any': 'warn',
"@tpmjs/ui/*/[!index]*",
"@tpmjs/utils/*",
"@tpmjs/types/*",
"@tpmjs/eslint-config/*",
"next/**",
"./.next/*",
],
},
],
"import/no-restricted-paths": [
"error",
{
zones: [
{ target: "./packages", from: "./apps" },
{ target: "./packages/ui", from: "./packages/utils" },
],
},
],
"import/no-anonymous-default-export": "error",
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_" },
],
"@typescript-eslint/no-explicit-any": "warn",
},
},
}
);

View file

@ -2,11 +2,9 @@
"name": "@tpmjs/eslint-config",
"version": "0.0.0",
"private": true,
"type": "module",
"main": "./base.js",
"files": [
"base.js",
"react.js"
],
"files": ["base.js", "react.js"],
"dependencies": {
"@eslint/js": "^9.18.0",
"eslint": "^9.18.0",

View file

@ -1,3 +0,0 @@
import reactConfig from '@tpmjs/eslint-config/react.js';
export default reactConfig;

View file

@ -0,0 +1,28 @@
import reactConfig from '@tpmjs/eslint-config/react.js';
export default [
{
ignores: ['dist/**', 'node_modules/**', 'eslint.config.mjs']
},
...reactConfig,
{
rules: {
'import/no-internal-modules': ['error', {
allow: [
'@tpmjs/ui/*/[!index]*',
'@tpmjs/utils/*',
'@tpmjs/types/*',
'@tpmjs/eslint-config/*',
'@testing-library/**',
'react/*',
]
}],
}
},
{
files: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'],
rules: {
'jsx-a11y/no-autofocus': 'off',
}
}
];

View file

@ -242,7 +242,7 @@ describe("Badge", () => {
let ref: HTMLDivElement | null = null;
render(
<Badge
ref={(el: HTMLDivElement | null) => {
ref={(el) => {
ref = el;
}}
>
@ -250,7 +250,7 @@ describe("Badge", () => {
</Badge>,
);
expect(ref).toBeInstanceOf(HTMLDivElement);
expect(ref?.tagName).toBe("DIV");
expect(ref!.tagName).toBe("DIV");
});
});

View file

@ -233,7 +233,7 @@ describe("Button", () => {
let ref: HTMLButtonElement | null = null;
render(
<Button
ref={(el: HTMLButtonElement | null) => {
ref={(el) => {
ref = el;
}}
>
@ -241,7 +241,7 @@ describe("Button", () => {
</Button>,
);
expect(ref).toBeInstanceOf(HTMLButtonElement);
expect(ref?.tagName).toBe("BUTTON");
expect(ref!.tagName).toBe("BUTTON");
});
});

View file

@ -409,7 +409,7 @@ describe("Card", () => {
let ref: HTMLDivElement | null = null;
render(
<Card
ref={(el: HTMLDivElement | null) => {
ref={(el) => {
ref = el;
}}
>
@ -417,7 +417,7 @@ describe("Card", () => {
</Card>,
);
expect(ref).toBeInstanceOf(HTMLDivElement);
expect(ref?.tagName).toBe("DIV");
expect(ref!.tagName).toBe("DIV");
});
it("forwards ref to CardHeader element", () => {
@ -425,7 +425,7 @@ describe("Card", () => {
render(
<Card>
<CardHeader
ref={(el: HTMLDivElement | null) => {
ref={(el) => {
ref = el;
}}
>
@ -442,7 +442,7 @@ describe("Card", () => {
<Card>
<CardHeader>
<CardTitle
ref={(el: HTMLHeadingElement | null) => {
ref={(el) => {
ref = el;
}}
>
@ -452,7 +452,7 @@ describe("Card", () => {
</Card>,
);
expect(ref).toBeInstanceOf(HTMLHeadingElement);
expect(ref?.tagName).toBe("H3");
expect(ref!.tagName).toBe("H3");
});
it("forwards ref to CardDescription element", () => {
@ -461,7 +461,7 @@ describe("Card", () => {
<Card>
<CardHeader>
<CardDescription
ref={(el: HTMLParagraphElement | null) => {
ref={(el) => {
ref = el;
}}
>
@ -478,7 +478,7 @@ describe("Card", () => {
render(
<Card>
<CardContent
ref={(el: HTMLDivElement | null) => {
ref={(el) => {
ref = el;
}}
>
@ -494,7 +494,7 @@ describe("Card", () => {
render(
<Card>
<CardFooter
ref={(el: HTMLDivElement | null) => {
ref={(el) => {
ref = el;
}}
>

View file

@ -42,8 +42,7 @@ export interface CardTitleProps extends HTMLAttributes<HTMLHeadingElement> {
/**
* CardDescription component props
*/
export interface CardDescriptionProps
extends HTMLAttributes<HTMLParagraphElement> {}
export type CardDescriptionProps = HTMLAttributes<HTMLParagraphElement>;
/**
* CardContent component props

View file

@ -287,13 +287,13 @@ describe("CodeBlock", () => {
render(
<CodeBlock
code="code"
ref={(el: HTMLDivElement | null) => {
ref={(el) => {
ref = el;
}}
/>,
);
expect(ref).toBeInstanceOf(HTMLDivElement);
expect(ref?.querySelector("code")).toBeInTheDocument();
expect(ref!.querySelector("code")).toBeInTheDocument();
});
});

View file

@ -215,7 +215,7 @@ describe("Container", () => {
let ref: HTMLDivElement | null = null;
render(
<Container
ref={(el: HTMLDivElement | null) => {
ref={(el) => {
ref = el;
}}
>
@ -223,7 +223,7 @@ describe("Container", () => {
</Container>,
);
expect(ref).toBeInstanceOf(HTMLDivElement);
expect(ref?.tagName).toBe("DIV");
expect(ref!.tagName).toBe("DIV");
});
});

View file

@ -258,7 +258,7 @@ describe("GridContainer", () => {
let ref: HTMLDivElement | null = null;
render(
<GridContainer
ref={(el: HTMLDivElement | null) => {
ref={(el) => {
ref = el;
}}
>

View file

@ -249,13 +249,13 @@ describe("Header", () => {
let ref: HTMLElement | null = null;
render(
<Header
ref={(el: HTMLElement | null) => {
ref={(el) => {
ref = el;
}}
/>,
);
expect(ref).toBeInstanceOf(HTMLElement);
expect(ref?.tagName).toBe("HEADER");
expect(ref!.tagName).toBe("HEADER");
});
});

View file

@ -179,7 +179,7 @@ describe("Icon", () => {
/>,
);
expect(ref).toBeInstanceOf(SVGSVGElement);
expect(ref?.tagName).toBe("svg");
expect(ref!.tagName).toBe("svg");
});
});

View file

@ -311,26 +311,26 @@ describe("Input", () => {
let ref: HTMLInputElement | null = null;
render(
<Input
ref={(el: HTMLInputElement | null) => {
ref={(el) => {
ref = el;
}}
/>,
);
expect(ref).toBeInstanceOf(HTMLInputElement);
expect(ref?.tagName).toBe("INPUT");
expect(ref!.tagName).toBe("INPUT");
});
it("can focus input through ref", () => {
let ref: HTMLInputElement | null = null;
render(
<Input
ref={(el: HTMLInputElement | null) => {
ref={(el) => {
ref = el;
}}
data-testid="input"
/>,
);
ref?.focus();
ref!.focus();
expect(screen.getByTestId("input")).toHaveFocus();
});
});

View file

@ -208,7 +208,7 @@ describe("Label", () => {
</Label>,
);
expect(ref).toBeInstanceOf(HTMLLabelElement);
expect(ref?.tagName).toBe("LABEL");
expect(ref!.tagName).toBe("LABEL");
});
});

View file

@ -221,7 +221,7 @@ describe("ProgressBar", () => {
render(
<ProgressBar
value={50}
ref={(el: HTMLDivElement | null) => {
ref={(el) => {
ref = el;
}}
/>,

View file

@ -284,7 +284,7 @@ describe("Section", () => {
let ref: HTMLElement | null = null;
render(
<Section
ref={(el: HTMLElement | null) => {
ref={(el) => {
ref = el;
}}
>
@ -292,7 +292,7 @@ describe("Section", () => {
</Section>,
);
expect(ref).toBeInstanceOf(HTMLElement);
expect(ref?.tagName).toBe("SECTION");
expect(ref!.tagName).toBe("SECTION");
});
});

View file

@ -370,7 +370,7 @@ describe("Tabs", () => {
tabs={mockTabs}
activeTab="all"
onTabChange={handleChange}
ref={(el: HTMLDivElement | null) => {
ref={(el) => {
ref = el;
}}
/>,