Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic ESLint config, lint script and lint CI job #1640

Draft
wants to merge 21 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist
node_modules
.github
.changeset
41 changes: 41 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module.exports = {
root: true,
extends: ['eslint:recommended', 'prettier'],
plugins: ['no-only-tests'],
env: {
browser: true,
node: true,
},
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
tsconfigRootDir: __dirname,
project: ['./tsconfig.json', './docs/tsconfig.json'],
},
rules: {
'no-only-tests/no-only-tests': 'warn',
},
overrides: [
{
files: ['**/*.ts'],
parser: '@typescript-eslint/parser',
extends: ['plugin:@typescript-eslint/recommended-type-checked'],
rules: {
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
},
},
{
files: ['**/env.d.ts'],
rules: {
'@typescript-eslint/triple-slash-reference': 'off',
},
},
],
};
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,23 @@ jobs:
- name: Test packages
run: pnpm -r test:coverage

lint:
name: Lint code
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- run: pnpm i
- name: Generate types
working-directory: ./docs
run: pnpm astro sync
- name: Run linter
run: pnpm lint

pa11y:
name: Check for accessibility issues
runs-on: ubuntu-20.04
Expand Down
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"description": "",
"scripts": {
"build:examples": "pnpm --no-bail --workspace-concurrency 1 --filter '@example/*' build",
"lint": "eslint . --max-warnings 0",
"size": "size-limit",
"version": "pnpm changeset version && pnpm i --no-frozen-lockfile",
"format": "prettier -w --cache --plugin prettier-plugin-astro ."
Expand All @@ -14,10 +15,16 @@
"@changesets/changelog-github": "^0.4.8",
"@changesets/cli": "^2.26.1",
"@size-limit/file": "^8.2.4",
"@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.3.1",
"astro": "^4.3.5",
"eslint": "^8.53.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-no-only-tests": "^3.1.0",
"prettier": "^3.0.0",
"prettier-plugin-astro": "^0.13.0",
"size-limit": "^8.2.4"
"size-limit": "^8.2.4",
"typescript": "^5.4.2"
},
"packageManager": "pnpm@8.7.4",
"size-limit": [
Expand Down
21 changes: 14 additions & 7 deletions packages/starlight/__tests__/basics/config-errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ test('parses valid config successfully', () => {

test('errors if title is missing', () => {
expect(() =>
parseStarlightConfigWithFriendlyErrors({} as any)
// @ts-expect-error Testing invalid config
parseStarlightConfigWithFriendlyErrors({})
).toThrowErrorMatchingInlineSnapshot(
`
"[AstroUserError]:
Expand All @@ -87,7 +88,8 @@ test('errors if title is missing', () => {

test('errors if title value is not a string', () => {
expect(() =>
parseStarlightConfigWithFriendlyErrors({ title: 5 } as any)
// @ts-expect-error Testing invalid config
parseStarlightConfigWithFriendlyErrors({ title: 5 })
).toThrowErrorMatchingInlineSnapshot(
`
"[AstroUserError]:
Expand All @@ -100,7 +102,8 @@ test('errors if title value is not a string', () => {

test('errors with bad social icon config', () => {
expect(() =>
parseStarlightConfigWithFriendlyErrors({ title: 'Test', social: { unknown: '' } as any })
// @ts-expect-error Testing invalid config
parseStarlightConfigWithFriendlyErrors({ title: 'Test', social: { unknown: '' } })
).toThrowErrorMatchingInlineSnapshot(
`
"[AstroUserError]:
Expand All @@ -114,7 +117,8 @@ test('errors with bad social icon config', () => {

test('errors with bad logo config', () => {
expect(() =>
parseStarlightConfigWithFriendlyErrors({ title: 'Test', logo: { html: '' } as any })
// @ts-expect-error Testing invalid config
parseStarlightConfigWithFriendlyErrors({ title: 'Test', logo: { html: '' } })
).toThrowErrorMatchingInlineSnapshot(
`
"[AstroUserError]:
Expand All @@ -131,7 +135,8 @@ test('errors with bad head config', () => {
expect(() =>
parseStarlightConfigWithFriendlyErrors({
title: 'Test',
head: [{ tag: 'unknown', attrs: { prop: null }, content: 20 } as any],
// @ts-expect-error Testing invalid config
head: [{ tag: 'unknown', attrs: { prop: null }, content: 20 }],
})
).toThrowErrorMatchingInlineSnapshot(
`
Expand All @@ -150,7 +155,8 @@ test('errors with bad sidebar config', () => {
expect(() =>
parseStarlightConfigWithFriendlyErrors({
title: 'Test',
sidebar: [{ label: 'Example', href: '/' } as any],
// @ts-expect-error Testing invalid config
sidebar: [{ label: 'Example', href: '/' }],
})
).toThrowErrorMatchingInlineSnapshot(
`
Expand All @@ -173,9 +179,10 @@ test('errors with bad nested sidebar config', () => {
label: 'Example',
items: [
{ label: 'Nested Example 1', link: '/' },
// @ts-expect-error Testing invalid config
{ label: 'Nested Example 2', link: true },
],
} as any,
},
],
})
).toThrowErrorMatchingInlineSnapshot(`
Expand Down
10 changes: 5 additions & 5 deletions packages/starlight/__tests__/basics/git.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ describe('getNewestCommitDate', () => {

test('throws when failing to retrieve the git history for a file', () => {
expect(() => getNewestCommitDate(getFilePath('../not-a-starlight-test-repo/test.md'))).toThrow(
/^Failed to retrieve the git history for file "[/\\-\w ]+\/test\.md"/
/^Failed to retrieve the git history for file "[/\\:\w -]+[/\\]test\.md"/
);
});

test('throws when trying to get the history of a non-existing or untracked file', () => {
const expectedError =
/^Failed to validate the timestamp for file "[/\\-\w ]+\/(?:unknown|untracked)\.md"$/;
/^Failed to validate the timestamp for file "[/\\:\w -]+[/\\](?:unknown|untracked)\.md"$/;
writeFile('untracked.md', 'content');

expect(() => getNewestCommitDate(getFilePath('unknown.md'))).toThrow(expectedError);
Expand Down Expand Up @@ -94,17 +94,17 @@ function makeTestRepo() {

return {
// The `dateStr` argument should be in the `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SSZ` format.
commitAllChanges(message: string, dateStr: ISODate) {
commitAllChanges: (message: string, dateStr: ISODate) => {
const date = dateStr.endsWith('Z') ? dateStr : `${dateStr}T00:00:00Z`;

runInRepo('git', ['add', '-A']);
// This sets both the author and committer dates to the provided date.
runInRepo('git', ['commit', '-m', message, '--date', date], { GIT_COMMITTER_DATE: date });
},
getFilePath(name: string) {
getFilePath: (name: string) => {
return join(repoPath, name);
},
writeFile(name: string, content: string) {
writeFile: (name: string, content: string) => {
writeFileSync(join(repoPath, name), content);
},
};
Expand Down
3 changes: 2 additions & 1 deletion packages/starlight/__tests__/basics/i18n.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('pickLang', () => {
});

test('returns undefined for unknown languages', () => {
expect(pickLang(dictionary, 'ar' as any)).toBeUndefined();
// @ts-expect-error Testing invalid input
expect(pickLang(dictionary, 'ar')).toBeUndefined();
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assert, expect, test, vi } from 'vitest';
import { expect, test, vi } from 'vitest';
import {
generateStarlightPageRouteData,
type StarlightPageProps,
Expand All @@ -23,7 +23,7 @@ const starlightPageProps: StarlightPageProps = {
test('throws a validation error if a built-in field required by the user schema is not passed down', async () => {
// The first line should be a user-friendly error message describing the exact issue and the second line should be
// the missing description field.
expect(() =>
await expect(() =>
generateStarlightPageRouteData({
props: starlightPageProps,
url: new URL('https://example.com/test-slug'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ test('supports `items` field for sidebar groups', async () => {
});

test('throws error if sidebar is malformated', async () => {
expect(() =>
await expect(() =>
generateStarlightPageRouteData({
props: {
...starlightPageProps,
Expand All @@ -236,7 +236,7 @@ test('throws error if sidebar is malformated', async () => {

test('throws error if sidebar uses wrong literal for entry type', async () => {
// This test also makes sure we show a helpful error for incorrect literals.
expect(() =>
await expect(() =>
generateStarlightPageRouteData({
props: {
...starlightPageProps,
Expand Down Expand Up @@ -441,7 +441,7 @@ test('disables table of contents for splash template', async () => {
});

test('hides the sidebar if the `hasSidebar` option is not specified and the splash template is used', async () => {
const { hasSidebar, ...otherProps } = starlightPageProps;
const { hasSidebar: _, ...otherProps } = starlightPageProps;
const data = await generateStarlightPageRouteData({
props: {
...otherProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ test('fallback routes use fallback entry last updated dates', () => {
});

expect(getNewestCommitDate).toHaveBeenCalledOnce();
expect(getNewestCommitDate.mock.lastCall?.[0]).toMatch(
expect(getNewestCommitDate.mock.lastCall?.[0]?.replace(/\\/g, '/')).toMatch(
/src\/content\/docs\/guides\/authoring-content.md$/
// ^ no `en/` prefix
);
Expand Down
8 changes: 4 additions & 4 deletions packages/starlight/__tests__/plugins/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ test('receives the user provided configuration including the plugins list', asyn

describe('validation', () => {
test('validates starlight configuration before running plugins', async () => {
expect(
await expect(
async () =>
await runPlugins(
// @ts-expect-error - invalid sidebar config.
Expand All @@ -64,7 +64,7 @@ describe('validation', () => {
});

test('validates plugins configuration before running them', async () => {
expect(
await expect(
async () =>
await runPlugins(
{ title: 'Test Docs' },
Expand All @@ -76,7 +76,7 @@ describe('validation', () => {
});

test('validates configuration updates from plugins do not update the `plugins` config key', async () => {
expect(
await expect(
async () =>
await runPlugins(
{ title: 'Test Docs' },
Expand All @@ -99,7 +99,7 @@ describe('validation', () => {
});

test('validates configuration updates from plugins', async () => {
expect(
await expect(
async () =>
await runPlugins(
{ title: 'Test Docs' },
Expand Down
6 changes: 3 additions & 3 deletions packages/starlight/__tests__/remark-rehype/asides.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ test('generates <aside>', async () => {
Some text
:::
`);
expect(res.code).toMatchFileSnapshot('./snapshots/generates-aside.html');
await expect(res.code).toMatchFileSnapshot('./snapshots/generates-aside.html');
});

describe('default labels', () => {
Expand Down Expand Up @@ -89,7 +89,7 @@ More.
</details>
:::
`);
expect(res.code).toMatchFileSnapshot('./snapshots/handles-complex-children.html');
await expect(res.code).toMatchFileSnapshot('./snapshots/handles-complex-children.html');
});

test('nested asides', async () => {
Expand All @@ -103,7 +103,7 @@ Nested tip.

::::
`);
expect(res.code).toMatchFileSnapshot('./snapshots/nested-asides.html');
await expect(res.code).toMatchFileSnapshot('./snapshots/nested-asides.html');
});

describe('translated labels in French', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ describe('validation', () => {
});

describe('processor', () => {
test('processes a basic tree', () => {
test('processes a basic tree', async () => {
const html = processTestFileTree(`<ul>
<li>root_file</li>
<li>root_directory/
Expand All @@ -61,7 +61,7 @@ describe('processor', () => {
<li>
</ul>`);

expect(extractFileTree(html)).toMatchFileSnapshot('./snapshots/file-tree-basic.html');
await expect(extractFileTree(html)).toMatchFileSnapshot('./snapshots/file-tree-basic.html');
});

test('does not add a comment node with no comments', () => {
Expand All @@ -70,18 +70,18 @@ describe('processor', () => {
expect(extractFileTree(html)).not.toContain('<span class="comment">');
});

test('processes text comments following the file name', () => {
test('processes text comments following the file name', async () => {
const html = processTestFileTree(`<ul><li>file this is a comment</li></ul>`);

expect(extractFileTree(html)).toMatchFileSnapshot('./snapshots/file-tree-comment-text.html');
await expect(extractFileTree(html)).toMatchFileSnapshot('./snapshots/file-tree-comment-text.html');
});

test('processes comment nodes', () => {
test('processes comment nodes', async () => {
const html = processTestFileTree(
`<ul><li>file this is an <strong>important</strong> comment</li></ul>`
);

expect(extractFileTree(html)).toMatchFileSnapshot('./snapshots/file-tree-comment-nodes.html');
await expect(extractFileTree(html)).toMatchFileSnapshot('./snapshots/file-tree-comment-nodes.html');
});

test('identifies directory with either a file name ending with a slash or a nested list', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/starlight/__tests__/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function mockDoc(
): StarlightDocsEntry {
return {
id,
slug: id.replace(/\.[^\.]+$/, '').replace(/\/index$/, ''),
slug: id.replace(/\.[^.]+$/, '').replace(/\/index$/, ''),
body,
collection: 'docs',
data: frontmatterSchema.parse(data),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PAGE_TITLE_ID } from '../../constants';

export class StarlightTOC extends HTMLElement {
private _current = this.querySelector('a[aria-current="true"]') as HTMLAnchorElement | null;
private _current = this.querySelector('a[aria-current="true"]');
private minH = parseInt(this.dataset.minH || '2', 10);
private maxH = parseInt(this.dataset.maxH || '3', 10);

Expand Down
1 change: 1 addition & 0 deletions packages/starlight/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
declare global {
// eslint-disable-next-line no-var
var StarlightThemeProvider: {
updatePickers(theme?: string): void;
};
Expand Down
Loading
Loading