From 631c37e3284edf1fc5a636e602d2bf53539f07d1 Mon Sep 17 00:00:00 2001 From: glypse Date: Sun, 24 May 2026 20:01:00 +0200 Subject: [PATCH] refactor: move config files to .ts, use eslint typeChecked rules Also includes some formatting fixes --- apps/web/eslint.config.js | 44 ----- apps/web/eslint.config.ts | 58 ++++++ apps/web/package.json | 2 + apps/web/src/app.d.ts | 4 +- .../components/docs/ComponentPreview.svelte | 30 +-- .../components/docs/DocShareActions.svelte | 52 ++--- .../components/docs/InstallationTabs.svelte | 6 +- .../lib/components/docs/MarkdownLayout.svelte | 7 +- .../docs/MobileDocShareActions.svelte | 135 +++++++------ .../lib/components/docs/ShikiCodeBlock.svelte | 5 +- .../components/docs/TableOfContents.svelte | 51 ++--- .../docs/markdown/Blockquote.svelte | 2 +- .../docs/markdown/CopyCodeButton.svelte | 13 +- .../docs/markdown/MarkdownPre.svelte | 2 +- .../lib/components/docs/markdown/Pre.svelte | 2 +- .../lib/components/docs/markdown/Step.svelte | 8 +- .../lib/components/docs/markdown/Steps.svelte | 7 +- .../lib/components/docs/markdown/Table.svelte | 2 +- .../docs/navigation/DocNavButton.svelte | 6 +- .../docs/navigation/DocNavigation.svelte | 11 +- .../docs/navigation/DocsSidebar.svelte | 39 ++-- .../docs/navigation/MobileSidebar.svelte | 38 ++-- .../docs/search/CommandPalette.svelte | 29 ++- .../docs/search/SearchTrigger.svelte | 6 +- .../src/lib/components/ui/ScrollArea.svelte | 32 +-- apps/web/src/lib/config/docs-ui.ts | 4 +- apps/web/src/lib/docs/frontmatter.ts | 6 +- apps/web/src/lib/docs/metadata.ts | 2 +- .../src/lib/stores/package-manager.svelte.ts | 6 +- apps/web/src/lib/utils/cn.ts | 2 +- apps/web/src/lib/utils/highlighter.ts | 13 +- apps/web/src/lib/utils/search.ts | 15 +- apps/web/src/lib/utils/use-portal.ts | 6 +- apps/web/src/routes/+layout.svelte | 5 +- apps/web/src/routes/+page.svelte | 5 +- apps/web/src/routes/docs/+layout.svelte | 37 ++-- .../src/routes/docs/og/[...slug]/+server.ts | 10 +- .../src/routes/docs/raw/[...slug]/+server.ts | 6 +- apps/web/svelte.config.js | 154 --------------- apps/web/svelte.config.ts | 186 ++++++++++++++++++ pnpm-lock.yaml | 8 +- 41 files changed, 582 insertions(+), 474 deletions(-) delete mode 100644 apps/web/eslint.config.js create mode 100644 apps/web/eslint.config.ts delete mode 100644 apps/web/svelte.config.js create mode 100644 apps/web/svelte.config.ts diff --git a/apps/web/eslint.config.js b/apps/web/eslint.config.js deleted file mode 100644 index b249eaa..0000000 --- a/apps/web/eslint.config.js +++ /dev/null @@ -1,44 +0,0 @@ -import js from '@eslint/js'; -import ts from 'typescript-eslint'; -import svelte from 'eslint-plugin-svelte'; -import globals from 'globals'; - -/** @type {import('eslint').Linter.Config[]} */ -export default [ - js.configs.recommended, - ...ts.configs.recommended, - ...svelte.configs['flat/recommended'], - { - languageOptions: { - globals: { - ...globals.browser, - ...globals.node - } - } - }, - { - files: ['**/*.svelte', '**/*.svelte.ts'], - languageOptions: { - parserOptions: { - parser: ts.parser - } - } - }, - { - rules: { - 'svelte/no-at-html-tags': 'off', - 'svelte/no-navigation-without-resolve': 'off', - '@typescript-eslint/no-unused-vars': [ - 'error', - { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - caughtErrorsIgnorePattern: '^_' - } - ] - } - }, - { - ignores: ['build/', '.svelte-kit/', 'dist/'] - } -]; diff --git a/apps/web/eslint.config.ts b/apps/web/eslint.config.ts new file mode 100644 index 0000000..f123d25 --- /dev/null +++ b/apps/web/eslint.config.ts @@ -0,0 +1,58 @@ +import js from '@eslint/js'; +import ts from 'typescript-eslint'; +import svelte from 'eslint-plugin-svelte'; +import globals from 'globals'; +import path from 'node:path'; +import prettier from 'eslint-config-prettier'; +import { includeIgnoreFile } from 'eslint/config'; +import { defineConfig } from 'eslint/config'; +import svelteConfig from './svelte.config'; + +const gitignorePath = path.resolve(import.meta.dirname, '.gitignore'); + +export default defineConfig( + includeIgnoreFile(gitignorePath), + js.configs.recommended, + ts.configs.strictTypeChecked, + ts.configs.stylisticTypeChecked, + svelte.configs.recommended, + prettier, + svelte.configs.prettier, + { + languageOptions: { + globals: { ...globals.browser, ...globals.node }, + parserOptions: { + projectService: true + } + }, + rules: { + // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects. + // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors + 'no-undef': 'off' + } + }, + { + files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], + languageOptions: { + parserOptions: { + projectService: true, + extraFileExtensions: ['.svelte'], + parser: ts.parser, + svelteConfig + } + } + }, + { + rules: { + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_' + } + ], + '@typescript-eslint/consistent-type-definitions': ['warn', 'type'] + } + } +); diff --git a/apps/web/package.json b/apps/web/package.json index b41f5e5..164461a 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -7,7 +7,9 @@ "@sveltejs/kit": "^2.53.0", "@sveltejs/vite-plugin-svelte": "^6.2.4", "@tailwindcss/vite": "^4.1.17", + "@types/hast": "^3.0.4", "@types/node": "^25.5.0", + "@types/unist": "^3.0.3", "@typescript-eslint/eslint-plugin": "^8.56.1", "@typescript-eslint/parser": "^8.56.1", "eslint": "^10.0.2", diff --git a/apps/web/src/app.d.ts b/apps/web/src/app.d.ts index d9a6117..fe94a99 100644 --- a/apps/web/src/app.d.ts +++ b/apps/web/src/app.d.ts @@ -11,8 +11,8 @@ declare global { } declare module '*.svx' { - import type { SvelteComponentTyped } from 'svelte'; - export default class extends SvelteComponentTyped> {} + import type { Component } from 'svelte'; + export default class extends Component> {} } declare module '*.svg?raw' { diff --git a/apps/web/src/lib/components/docs/ComponentPreview.svelte b/apps/web/src/lib/components/docs/ComponentPreview.svelte index 5036ef3..79bc320 100644 --- a/apps/web/src/lib/components/docs/ComponentPreview.svelte +++ b/apps/web/src/lib/components/docs/ComponentPreview.svelte @@ -38,7 +38,7 @@ ...restProps }: ComponentProps = $props(); componentPreviewCounter += 1; - const tabsInstanceId = `component-preview-${componentPreviewCounter}`; + const tabsInstanceId = `component-preview-${componentPreviewCounter.toString()}`; const panelId = `${tabsInstanceId}-panel`; let previewKey = $state(0); @@ -46,7 +46,7 @@ const tabs = $derived( (() => { const normalized = - providedSources?.filter((tab): tab is SourceTab => Boolean(tab?.code)) ?? []; + providedSources?.filter((tab): tab is SourceTab => Boolean(tab.code)) ?? []; if (normalized.length > 0) { return normalized; @@ -63,7 +63,7 @@ } return []; - })() as SourceTab[] + })() ); let activeTab = $state(0); @@ -71,8 +71,8 @@ const selectedTab = $derived( tabs.length === 0 ? 0 : Math.min(activeTab, Math.max(0, tabs.length - 1)) ); - const activeSource = $derived((tabs.at(selectedTab) ?? null) as SourceTab | null); - const activeTabId = $derived(`${tabsInstanceId}-tab-${selectedTab}`); + const activeSource = $derived(tabs.at(selectedTab) ?? null); + const activeTabId = $derived(`${tabsInstanceId}-tab-${selectedTab.toString()}`); async function highlightTabs( sources: SourceTab[] @@ -104,7 +104,7 @@ } function focusTabByIndex(index: number) { - const tabElement = document.getElementById(`${tabsInstanceId}-tab-${index}`); + const tabElement = document.getElementById(`${tabsInstanceId}-tab-${index.toString()}`); if (tabElement instanceof HTMLButtonElement) { tabElement.focus(); } @@ -162,7 +162,9 @@ >
{#key previewKey} - {@render children?.()} + {#if children} + {@render children()} + {/if} {/key}
@@ -183,7 +185,7 @@ {#each tabs as tab, index (tab.name)} @@ -231,8 +237,8 @@ {:catch}
{activeSource.code}
{/await} - {:else} - {@render codeSlot?.()} + {:else if codeSlot} + {@render codeSlot()} {/if} diff --git a/apps/web/src/lib/components/docs/DocShareActions.svelte b/apps/web/src/lib/components/docs/DocShareActions.svelte index 11fc67f..35ede5d 100644 --- a/apps/web/src/lib/components/docs/DocShareActions.svelte +++ b/apps/web/src/lib/components/docs/DocShareActions.svelte @@ -54,11 +54,15 @@ } const content = await response.text(); - if (typeof navigator === 'undefined' || !navigator.clipboard?.writeText) { + if (typeof navigator === 'undefined') { throw new Error('Clipboard unavailable'); } - await navigator.clipboard.writeText(content); + try { + await navigator.clipboard.writeText(content); + } catch (_err: unknown) { + throw new Error('Clipboard unavailable', { cause: _err }); + } copyState = 'success'; } catch { copyState = 'error'; @@ -86,7 +90,7 @@ {#if canShowCopy} {/if} - {#if canShowRepository} - - - {docsUiConfig.docActions.repositoryLinkLabel} - - {opensInNewTabLabel} - - {/if} + + {docsUiConfig.docActions.repositoryLinkLabel} + + {opensInNewTabLabel} + + {/if} {#if chatGptUrl} - {docsUiConfig.docActions.assistants.chatgpt.label} - - {opensInNewTabLabel} - - {/if} + {docsUiConfig.docActions.assistants.chatgpt.label} + + {opensInNewTabLabel} + + {/if} {#if claudeUrl} - {docsUiConfig.docActions.assistants.claude.label} - - {opensInNewTabLabel} - - {/if} + {docsUiConfig.docActions.assistants.claude.label} + + {opensInNewTabLabel} + + {/if} {/if} diff --git a/apps/web/src/lib/components/docs/InstallationTabs.svelte b/apps/web/src/lib/components/docs/InstallationTabs.svelte index 4acd13e..f22c140 100644 --- a/apps/web/src/lib/components/docs/InstallationTabs.svelte +++ b/apps/web/src/lib/components/docs/InstallationTabs.svelte @@ -44,7 +44,7 @@ }); $effect(() => { - getHighlighter().then((highlighter) => { + void getHighlighter().then((highlighter) => { for (const pm of packageManagers) { const cmd = commands[pm]; highlightedCommands[pm] = { @@ -95,8 +95,8 @@ {#if highlightedCommands[packageManagerStore.active]} {:else} diff --git a/apps/web/src/lib/components/docs/MarkdownLayout.svelte b/apps/web/src/lib/components/docs/MarkdownLayout.svelte index ecf317c..35fb950 100644 --- a/apps/web/src/lib/components/docs/MarkdownLayout.svelte +++ b/apps/web/src/lib/components/docs/MarkdownLayout.svelte @@ -29,12 +29,15 @@
- {@render children?.()} + {#if children} + {@render children()} + {/if}
diff --git a/apps/web/src/lib/components/docs/MobileDocShareActions.svelte b/apps/web/src/lib/components/docs/MobileDocShareActions.svelte index b31e564..e841983 100644 --- a/apps/web/src/lib/components/docs/MobileDocShareActions.svelte +++ b/apps/web/src/lib/components/docs/MobileDocShareActions.svelte @@ -81,6 +81,7 @@ let success = false; // 1. Try modern Clipboard API + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (canUseWindow && navigator?.clipboard?.writeText) { try { await navigator.clipboard.writeText(content); @@ -106,8 +107,11 @@ textArea.focus(); textArea.select(); + // Using deprecated APIs as a last-resort fallback for older browsers. + // eslint-disable-next-line @typescript-eslint/no-deprecated const supported = document.queryCommandSupported('copy'); if (supported) { + // eslint-disable-next-line @typescript-eslint/no-deprecated success = document.execCommand('copy'); } document.body.removeChild(textArea); @@ -143,9 +147,9 @@ } isDropdownOpen = true; updatePosition(); - tick().then(() => { - const [first] = getMenuItems(); - first?.focus(); + void tick().then(() => { + const items = getMenuItems(); + if (items.length > 0) items[0].focus(); }); } @@ -201,7 +205,8 @@ if (event.key === 'ArrowUp') { event.preventDefault(); - const nextIndex = activeIndex >= 0 ? (activeIndex - 1 + items.length) % items.length : items.length - 1; + const nextIndex = + activeIndex >= 0 ? (activeIndex - 1 + items.length) % items.length : items.length - 1; items[nextIndex]?.focus(); return; } @@ -230,7 +235,7 @@ const trigger = getTriggerElement(); if (!trigger || !canUseWindow) return; const rect = trigger.getBoundingClientRect(); - dropdownStyle = `top: ${rect.bottom + 8}px; right: ${window.innerWidth - rect.right}px; position: fixed;`; + dropdownStyle = `top: ${(rect.bottom + 8).toString()}px; right: ${(window.innerWidth - rect.right).toString()}px; position: fixed;`; } onMount(() => { @@ -280,7 +285,7 @@
+ + {#if isDropdownOpen} + diff --git a/apps/web/src/lib/components/docs/ShikiCodeBlock.svelte b/apps/web/src/lib/components/docs/ShikiCodeBlock.svelte index a06fe82..60f96f7 100644 --- a/apps/web/src/lib/components/docs/ShikiCodeBlock.svelte +++ b/apps/web/src/lib/components/docs/ShikiCodeBlock.svelte @@ -30,7 +30,10 @@
-	
normalizedHtmlLight)}>
+
normalizedHtmlLight)} + >
normalizedHtmlDark)}>
diff --git a/apps/web/src/lib/components/docs/TableOfContents.svelte b/apps/web/src/lib/components/docs/TableOfContents.svelte index 1548653..b497c32 100644 --- a/apps/web/src/lib/components/docs/TableOfContents.svelte +++ b/apps/web/src/lib/components/docs/TableOfContents.svelte @@ -129,17 +129,17 @@ if (points.length === 0) return ''; if (points.length === 1) { const [point] = points; - return `M ${point.x} ${point.y}`; + return `M ${point.x.toString()} ${point.y.toString()}`; } - const commands: string[] = [`M ${points[0].x} ${points[0].y}`]; + const commands: string[] = [`M ${points[0].x.toString()} ${points[0].y.toString()}`]; for (let i = 1; i < points.length; i++) { const point = points[i]; const prev = points[i - 1]; if (i === points.length - 1) { - commands.push(` L ${point.x} ${point.y}`); + commands.push(` L ${point.x.toString()} ${point.y.toString()}`); continue; } @@ -152,7 +152,7 @@ const nextLen = Math.hypot(nextVecX, nextVecY); if (prevLen === 0 || nextLen === 0) { - commands.push(` L ${point.x} ${point.y}`); + commands.push(` L ${point.x.toString()} ${point.y.toString()}`); continue; } @@ -163,7 +163,7 @@ const dot = prevDirX * nextDirX + prevDirY * nextDirY; if (Math.abs(dot) > 0.999) { - commands.push(` L ${point.x} ${point.y}`); + commands.push(` L ${point.x.toString()} ${point.y.toString()}`); continue; } @@ -173,8 +173,10 @@ const exitX = point.x + nextDirX * cornerRadius; const exitY = point.y + nextDirY * cornerRadius; - commands.push(` L ${entryX} ${entryY}`); - commands.push(` Q ${point.x} ${point.y} ${exitX} ${exitY}`); + commands.push(` L ${entryX.toString()} ${entryY.toString()}`); + commands.push( + ` Q ${point.x.toString()} ${point.y.toString()} ${exitX.toString()} ${exitY.toString()}` + ); } return commands.join(''); @@ -245,8 +247,8 @@ if (range) { indicatorRange = range; - } else if (!indicatorRange) { - indicatorRange = appliedRange; + } else { + indicatorRange ??= appliedRange; } const startPos = linkPositions.get(appliedRange.startId); @@ -306,20 +308,21 @@ const parsed: TocItem[] = []; for (const node of nodeList) { - const text = node.textContent?.trim() ?? ''; + const rawText = node.textContent; + const text = rawText ? rawText.trim() : ''; if (!text) continue; let id = node.id; if (!id) { let baseSlug = slugify(text); if (!baseSlug) { - baseSlug = `section-${parsed.length + 1}`; + baseSlug = `section-${(parsed.length + 1).toString()}`; } const count = slugCounts.get(baseSlug); if (typeof count === 'number') { const nextCount = count + 1; slugCounts.set(baseSlug, nextCount); - baseSlug = `${baseSlug}-${nextCount}`; + baseSlug = `${baseSlug}-${nextCount.toString()}`; } else { slugCounts.set(baseSlug, 0); } @@ -332,7 +335,7 @@ do { nextCount += 1; - id = `${baseId}-${nextCount}`; + id = `${baseId}-${nextCount.toString()}`; } while (usedIds.has(id)); slugCounts.set(baseId, nextCount); @@ -366,7 +369,9 @@ indicatorBottom = 0; indicatorRange = null; - requestAnimationFrame(() => updateLayout()); + requestAnimationFrame(() => { + updateLayout(); + }); if (!parsed.length) { return undefined; @@ -401,11 +406,9 @@ } const last = parsed[parsed.length - 1]; - if (last) { - const scrolledBottom = scrollY + viewportHeight; - if (scrolledBottom >= scrollHeight - 20) { - current = last.id; - } + const scrolledBottom = scrollY + viewportHeight; + if (scrolledBottom >= scrollHeight - 20) { + current = last.id; } activeId = current; @@ -563,8 +566,8 @@
{/if} @@ -594,7 +597,7 @@ {#each headings as heading (heading.id)}
  • diff --git a/apps/web/src/lib/components/docs/markdown/CopyCodeButton.svelte b/apps/web/src/lib/components/docs/markdown/CopyCodeButton.svelte index 648f5cc..b8077d2 100644 --- a/apps/web/src/lib/components/docs/markdown/CopyCodeButton.svelte +++ b/apps/web/src/lib/components/docs/markdown/CopyCodeButton.svelte @@ -4,21 +4,14 @@ import Copy from 'carbon-icons-svelte/lib/Copy.svelte'; import Checkmark from 'carbon-icons-svelte/lib/Checkmark.svelte'; - type Props = { - code: string; - class?: string; - }; - - const props = $props(); - const className = $derived((props as Props).class ?? ''); - const code = $derived((props as Props).code ?? ''); + let { code, class: className }: { code: string; class: string } = $props(); let copied = $state(false); let timeoutId: number | null = null; let lastCode: string | null = null; async function handleCopy(value: string) { - if (!value || typeof navigator === 'undefined' || !navigator.clipboard) { + if (!value || typeof navigator === 'undefined') { return; } @@ -67,7 +60,7 @@ onclick={(event) => { event.stopPropagation(); event.preventDefault(); - handleCopy(code); + void handleCopy(code); }} aria-label={copied ? 'Copied code' : 'Copy code'} > diff --git a/apps/web/src/lib/components/docs/markdown/MarkdownPre.svelte b/apps/web/src/lib/components/docs/markdown/MarkdownPre.svelte index 27b2648..582982a 100644 --- a/apps/web/src/lib/components/docs/markdown/MarkdownPre.svelte +++ b/apps/web/src/lib/components/docs/markdown/MarkdownPre.svelte @@ -21,7 +21,7 @@ {#if code}
    -		{@render code?.()}
    +		{@render code()}
     	
    {:else} diff --git a/apps/web/src/lib/components/docs/markdown/Pre.svelte b/apps/web/src/lib/components/docs/markdown/Pre.svelte index 1e9baac..0ff7c58 100644 --- a/apps/web/src/lib/components/docs/markdown/Pre.svelte +++ b/apps/web/src/lib/components/docs/markdown/Pre.svelte @@ -35,7 +35,7 @@ class={cn( unstyled ? 'group/pre relative font-mono text-base font-normal' - : 'group/pre card relative rounded-md bg-background p-4 font-mono text-base font-normal text-foreground', + : 'group/pre relative rounded-md bg-background p-4 font-mono text-base font-normal text-foreground card', className )} > diff --git a/apps/web/src/lib/components/docs/markdown/Step.svelte b/apps/web/src/lib/components/docs/markdown/Step.svelte index 3f657cf..e6f1ce6 100644 --- a/apps/web/src/lib/components/docs/markdown/Step.svelte +++ b/apps/web/src/lib/components/docs/markdown/Step.svelte @@ -6,11 +6,11 @@ class: className, children, title - } = $props<{ + }: { class?: string; children?: Snippet; title?: string; - }>(); + } = $props();
    @@ -20,7 +20,7 @@ class="inset-shadow absolute -left-5 flex size-10 rounded-full bg-background-inset p-1.5" >
    @@ -31,7 +31,7 @@ {:else}
    diff --git a/apps/web/src/lib/components/docs/markdown/Steps.svelte b/apps/web/src/lib/components/docs/markdown/Steps.svelte index 9b96a73..eb42d00 100644 --- a/apps/web/src/lib/components/docs/markdown/Steps.svelte +++ b/apps/web/src/lib/components/docs/markdown/Steps.svelte @@ -2,10 +2,13 @@ import { cn } from '$lib/utils/cn'; import type { Snippet } from 'svelte'; - let { class: className, children } = $props<{ + let { + class: className, + children + }: { class?: string; children?: Snippet; - }>(); + } = $props();
    -
    +
    {@render children?.()} diff --git a/apps/web/src/lib/components/docs/navigation/DocNavButton.svelte b/apps/web/src/lib/components/docs/navigation/DocNavButton.svelte index c6b15ee..cd4cc0b 100644 --- a/apps/web/src/lib/components/docs/navigation/DocNavButton.svelte +++ b/apps/web/src/lib/components/docs/navigation/DocNavButton.svelte @@ -1,4 +1,5 @@ -{#if docsUiConfig.pagination.enabled && (previous || next)} +{#if docsUiConfig.pagination.enabled && (previous ?? next)}