Skip to content

Commit d1b3828

Browse files
hippotasticdelucisHiDeoo
authored
Add basic ESLint config, lint script and lint CI job (#1640)
Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> Co-authored-by: HiDeoo <494699+HiDeoo@users.noreply.github.com> Co-authored-by: delucis <357379+delucis@users.noreply.github.com>
1 parent 1603885 commit d1b3828

50 files changed

Lines changed: 988 additions & 135 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/new-ads-hug.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@astrojs/starlight': patch
3+
---
4+
5+
Refactors various internal systems, improving code quality and maintainability.

.github/workflows/ci.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,23 @@ jobs:
9191
- name: Type check packages
9292
run: pnpm typecheck
9393

94+
lint:
95+
name: Lint code
96+
runs-on: ubuntu-latest
97+
steps:
98+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
99+
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
100+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
101+
with:
102+
node-version: ${{ env.NODE_VERSION }}
103+
cache: 'pnpm'
104+
- run: pnpm i
105+
- name: Generate types
106+
working-directory: ./docs
107+
run: pnpm astro sync
108+
- name: Run linter
109+
run: pnpm lint
110+
94111
a11y:
95112
name: Check for accessibility issues
96113
runs-on: ubuntu-latest

.vscode/extensions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"recommendations": ["astro-build.astro-vscode"],
2+
"recommendations": ["astro-build.astro-vscode", "dbaeumer.vscode-eslint"],
33
"unwantedRecommendations": []
44
}

docs/src/components/theme-designer/atom.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ export function map<T extends Record<string, unknown>>(value: T): MapStore<T> {
3030
return atom;
3131
}
3232

33-
export function atom<T extends unknown>(value: T): Atom<T> {
33+
export function atom<T>(value: T): Atom<T> {
3434
return new Atom(value);
3535
}

eslint.config.mjs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// @ts-check
2+
import { dirname } from 'node:path';
3+
import { fileURLToPath } from 'node:url';
4+
import eslint from '@eslint/js';
5+
import tseslint from 'typescript-eslint';
6+
import { globalIgnores } from 'eslint/config';
7+
import globals from 'globals';
8+
import prettierConfig from 'eslint-config-prettier';
9+
10+
export default tseslint.config(
11+
// Ignore hidden files and directories, `*.d.ts` files (as most recommendations are mostly for
12+
// users rather than libraries), types testing files, example directories, and build directories.
13+
globalIgnores([
14+
'**/.*',
15+
'**/*.d.ts',
16+
'**/*.test-d.ts',
17+
'**/examples/',
18+
'**/dist/',
19+
'**/build/',
20+
'**/examples/',
21+
]),
22+
23+
// Setup Node.js globals from `globalThis` (does not include CommonJS arguments).
24+
{
25+
languageOptions: {
26+
globals: {
27+
...globals.nodeBuiltin,
28+
},
29+
},
30+
},
31+
32+
// Add ESLint recommended rules.
33+
eslint.configs.recommended,
34+
35+
// Add TypeScript ESLint recommended rules with type checking.
36+
tseslint.configs.recommendedTypeChecked,
37+
// Setup typed linting.
38+
{
39+
languageOptions: {
40+
parserOptions: {
41+
projectService: true,
42+
tsconfigRootDir: dirname(fileURLToPath(import.meta.url)),
43+
},
44+
},
45+
},
46+
// Disabled typed linting in JavaScript files.
47+
{
48+
files: ['**/*.js', '**/*.cjs', '**/*.mjs'],
49+
extends: [tseslint.configs.disableTypeChecked],
50+
},
51+
52+
// Disable all formatting rules.
53+
prettierConfig,
54+
55+
// Tweak some rules in all files.
56+
{
57+
rules: {
58+
// Allow triple-slash references that we heavily use.
59+
'@typescript-eslint/triple-slash-reference': 'off',
60+
// Disable unbound method checks which requires another plugin to properly work with `expect`
61+
// calls.
62+
'@typescript-eslint/unbound-method': 'off',
63+
// Allow empty catch blocks.
64+
'no-empty': ['error', { allowEmptyCatch: true }],
65+
// Allow unused variables for sibling properties in destructuring (used to omit properties)
66+
// or starting with `_`.
67+
'@typescript-eslint/no-unused-vars': [
68+
'error',
69+
{ ignoreRestSiblings: true, destructuredArrayIgnorePattern: '^_', varsIgnorePattern: '^_' },
70+
],
71+
// Allow using `any` in rest parameter arrays, e.g. `(...args: any[]) => void`.
72+
'@typescript-eslint/no-explicit-any': ['error', { ignoreRestArgs: true }],
73+
// Allow duplicated types in unions as it's not an error and the extra verbosity can make it
74+
// easier to understand some unions, e.g. for Zod input and output types.
75+
'@typescript-eslint/no-duplicate-type-constituents': 'off',
76+
// Allow redundant types in unions as it's not an error and we use such mechanisms to provide
77+
// fallbacks for some types that may not be accessible in some user environments, e.g. i18n
78+
// keys for plugins.
79+
'@typescript-eslint/no-redundant-type-constituents': 'off',
80+
},
81+
}
82+
);

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"description": "",
66
"scripts": {
77
"build:examples": "pnpm --no-bail --workspace-concurrency 1 --filter '@example/*' build",
8+
"lint": "eslint . --max-warnings 0",
89
"size": "size-limit",
910
"version": "pnpm changeset version && pnpm i --no-frozen-lockfile",
1011
"format": "prettier -w --cache --plugin prettier-plugin-astro .",
@@ -15,12 +16,17 @@
1516
"@astrojs/check": "^0.9.4",
1617
"@changesets/changelog-github": "^0.5.0",
1718
"@changesets/cli": "^2.27.9",
19+
"@eslint/js": "^9.33.0",
1820
"@size-limit/file": "^11.1.6",
1921
"astro": "^5.6.1",
22+
"eslint": "^9.33.0",
23+
"eslint-config-prettier": "^10.1.8",
24+
"globals": "^16.3.0",
2025
"prettier": "^3.3.3",
2126
"prettier-plugin-astro": "^0.14.1",
2227
"size-limit": "^11.1.6",
23-
"typescript": "^5.6.3"
28+
"typescript": "^5.6.3",
29+
"typescript-eslint": "^8.39.1"
2430
},
2531
"packageManager": "pnpm@9.15.9",
2632
"size-limit": [

packages/file-icons-generator/utils/font.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export async function getIconSvgPaths(repoPath: string, icons: string[], definit
2626
try {
2727
// Find the glyph matching the icon name.
2828
glyph = font.nameToGlyph(icon);
29-
} catch (error) {
29+
} catch {
3030
// If the glyph is not found, this means that multiple icons share the same glyph and we have
3131
// a mapping for such case.
3232
const alias = getFontGlyphAlias(icon);

packages/starlight/__e2e__/basics.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ test.describe('components', () => {
175175
await expectSelectedTab(pkgTabsB, 'pnpm', 'another pnpm command');
176176
await expectSelectedTab(pkgTabsC, 'pnpm', 'another pnpm command');
177177

178-
page.reload();
178+
await page.reload();
179179

180180
// The synced tabs with a persisted state should be restored.
181181
await expectSelectedTab(pkgTabsA, 'pnpm', 'pnpm command');
@@ -204,7 +204,7 @@ test.describe('components', () => {
204204

205205
await expectSelectedTab(styleTabs, 'tailwind', 'tailwind code');
206206

207-
page.reload();
207+
await page.reload();
208208

209209
// The synced tabs with a persisted state should be restored.
210210
await expectSelectedTab(styleTabs, 'tailwind', 'tailwind code');
@@ -234,7 +234,7 @@ test.describe('components', () => {
234234
// Select the windows tab in the set of tabs synced with the 'os' key.
235235
await osTabsB.getByRole('tab').filter({ hasText: 'windows' }).click();
236236

237-
page.reload();
237+
await page.reload();
238238

239239
// The synced tabs with a persisted state for the `pkg` sync key should be restored.
240240
await expectSelectedTab(pkgTabsA, 'pnpm', 'pnpm command');
@@ -298,7 +298,7 @@ test.describe('components', () => {
298298
'invalid-value'
299299
);
300300

301-
page.reload();
301+
await page.reload();
302302

303303
// The synced tabs should not be restored due to the invalid persisted state.
304304
await expectSelectedTab(pkgTabsA, 'npm', 'npm command');
@@ -334,7 +334,7 @@ test.describe('components', () => {
334334
await expectSelectedTab(pkgTabs, 'pnpm');
335335
await expectSelectedTab(osTabsB, 'linux', 'pnpm GNU/Linux');
336336

337-
page.reload();
337+
await page.reload();
338338

339339
// The synced tabs should be restored.
340340
await expectSelectedTab(pkgTabs, 'pnpm');

packages/starlight/__tests__/basics/config-errors.test.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ test('parses bare minimum valid config successfully', () => {
9191

9292
test('errors if title is missing', () => {
9393
expect(() =>
94-
parseStarlightConfigWithFriendlyErrors({} as any)
94+
// @ts-expect-error - Testing invalid config
95+
parseStarlightConfigWithFriendlyErrors({})
9596
).toThrowErrorMatchingInlineSnapshot(
9697
`
9798
"[AstroUserError]:
@@ -105,7 +106,8 @@ test('errors if title is missing', () => {
105106

106107
test('errors if title value is not a string or an Object', () => {
107108
expect(() =>
108-
parseStarlightConfigWithFriendlyErrors({ title: 5 } as any)
109+
// @ts-expect-error - Testing invalid config
110+
parseStarlightConfigWithFriendlyErrors({ title: 5 })
109111
).toThrowErrorMatchingInlineSnapshot(
110112
`
111113
"[AstroUserError]:
@@ -119,7 +121,8 @@ test('errors if title value is not a string or an Object', () => {
119121

120122
test('errors with bad social icon config', () => {
121123
expect(() =>
122-
parseStarlightConfigWithFriendlyErrors({ title: 'Test', social: { unknown: '' } as any })
124+
// @ts-expect-error - Testing invalid config
125+
parseStarlightConfigWithFriendlyErrors({ title: 'Test', social: { unknown: '' } })
123126
).toThrowErrorMatchingInlineSnapshot(
124127
`
125128
"[AstroUserError]:
@@ -135,7 +138,8 @@ test('errors with bad social icon config', () => {
135138

136139
test('errors with bad logo config', () => {
137140
expect(() =>
138-
parseStarlightConfigWithFriendlyErrors({ title: 'Test', logo: { html: '' } as any })
141+
// @ts-expect-error - Testing invalid config
142+
parseStarlightConfigWithFriendlyErrors({ title: 'Test', logo: { html: '' } })
139143
).toThrowErrorMatchingInlineSnapshot(
140144
`
141145
"[AstroUserError]:
@@ -152,7 +156,8 @@ test('errors with bad head config', () => {
152156
expect(() =>
153157
parseStarlightConfigWithFriendlyErrors({
154158
title: 'Test',
155-
head: [{ tag: 'unknown', attrs: { prop: null }, content: 20 } as any],
159+
// @ts-expect-error - Testing invalid config
160+
head: [{ tag: 'unknown', attrs: { prop: null }, content: 20 }],
156161
})
157162
).toThrowErrorMatchingInlineSnapshot(
158163
`
@@ -171,7 +176,8 @@ test('errors with bad sidebar config', () => {
171176
expect(() =>
172177
parseStarlightConfigWithFriendlyErrors({
173178
title: 'Test',
174-
sidebar: [{ label: 'Example', href: '/' } as any],
179+
// @ts-expect-error - Testing invalid config
180+
sidebar: [{ label: 'Example', href: '/' }],
175181
})
176182
).toThrowErrorMatchingInlineSnapshot(
177183
`
@@ -194,9 +200,10 @@ test('errors with bad nested sidebar config', () => {
194200
label: 'Example',
195201
items: [
196202
{ label: 'Nested Example 1', link: '/' },
203+
// @ts-expect-error - Testing invalid config
197204
{ label: 'Nested Example 2', link: true },
198205
],
199-
} as any,
206+
},
200207
],
201208
})
202209
).toThrowErrorMatchingInlineSnapshot(`

packages/starlight/__tests__/basics/format-canonical.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ describe.each<{
6868
])(
6969
'formatCanonical() with { format: $options.format, trailingSlash: $options.trailingSlash }',
7070
({ options, tests }) => {
71-
test.each(tests)('returns $expected for $href', async ({ href, expected }) => {
71+
test.each(tests)('returns $expected for $href', ({ href, expected }) => {
7272
expect(formatCanonical(href, options)).toBe(expected);
7373
});
7474
}

0 commit comments

Comments
 (0)