clean up with knip in web

This commit is contained in:
swithek 2026-08-16 21:52:19 +02:00
parent 0382d19292
commit 100f3bd3ab
5 changed files with 16 additions and 127 deletions

View File

@ -127,7 +127,7 @@ ESLint ([eslint.config.mjs](eslint.config.mjs)) runs **type-aware**: `eslint.con
- `@typescript-eslint/no-explicit-any` is off; `prefer-function-type` is off (keeps the `defineEmits<{ (e: ...): void }>()` style).
- The vendored `packages/lezer-promql` fork is excluded so upstream re-syncs cannot break the gate.
**knip** ([knip.ts](knip.ts)) guards dead exports, unused files, and unused dependencies. Its blind spots are deliberate and documented in the config: the auto-import surface (`app/composables`, `app/stores`, `app/utils`) and all `.vue` components are entry points because nuxt wires them without import statements — unused exports there and unused components are **not** reported. `app/components/shadcn/` is ignored (vendored spares are expected). Knip's `unlisted` check is **off** by policy: the hoisted node_modules layout makes transitive packages importable, that is relied on deliberately, and `package.json` declares top-level intent only.
**knip** ([knip.ts](knip.ts)) guards dead exports, unused files, and unused dependencies. Its nuxt plugin resolves auto-imports through the generated `.nuxt` maps, so unused components, stores, and utils **are** detected despite having no import statements. Remaining blind spots, documented in the config: `app/composables` are entry points (the auto-import map does not connect their `export default function useX` style, so their internal exports are unreported), and `app/components/shadcn/` is ignored (vendored spares are expected). Knip's `unlisted` check is **off** by policy: the hoisted node_modules layout makes transitive packages importable, that is relied on deliberately, and `package.json` declares top-level intent only. Knip runs with `--cache`, and the cache is keyed by knip version, **not** config — after editing [knip.ts](knip.ts), run `rm -rf node_modules/.cache/knip` or results are stale.
TypeScript strictness (set in [nuxt.config.ts](nuxt.config.ts)): `noUnusedLocals`, `noUnusedParameters`, `noUncheckedIndexedAccess`, `noImplicitOverride`, `verbatimModuleSyntax`, and `noImplicitAny` all on; `allowUnreachableCode: false` (mirrored in [electron/tsconfig.json](electron/tsconfig.json)). Path aliases `@/*` and `~/*` both point to `app/*`.

View File

@ -1,37 +0,0 @@
<script lang="ts" setup>
import type { InputProps } from "./shadcn/ui/input"
import { cn } from "~/lib/utils"
// NOTE: currently, only "ghost" inputs are supported
const props = defineProps<
Omit<InputProps, "modelValue" | "ghost"> & {
inputClass?: string
}
>()
const value = defineModel<string>()
</script>
<template>
<div :class="cn('relative inline-grid min-w-0', props.class)">
<span
aria-hidden="true"
:class="
cn(
props.inputClass,
'pointer-events-none invisible block whitespace-pre [grid-area:1/1]',
)
"
>
{{ value || props.placeholder }}
</span>
<ShadcnUiInput
v-model="value"
v-bind="{
...props,
ghost: true,
class: cn(props.inputClass, 'absolute inset-0 w-full [grid-area:1/1]'),
}"
/>
</div>
</template>

View File

@ -2,7 +2,7 @@
import IconStack from "./IconStack.vue"
import { showToastMessage } from "../toast"
export interface ReviewerEntry {
interface ReviewerEntry {
id: string
name: string
email: string
@ -11,7 +11,7 @@ export interface ReviewerEntry {
previouslyApproved?: boolean
}
export interface ReviewerInviteEntry {
interface ReviewerInviteEntry {
id: string
name: string
email?: string | null

View File

@ -1,32 +0,0 @@
<script setup lang="ts">
import ConfigField from "./ConfigField.vue"
const props = withDefaults(
defineProps<{
disabled?: boolean
placeholder: string
inputClass?: string
}>(),
{
inputClass: "text-2sm md:text-2sm px-2 h-[1.775rem]",
},
)
const value = defineModel<string | undefined>()
</script>
<template>
<ConfigField>
<template #label>
<slot name="label" />
</template>
<template #default>
<ShadcnUiInput
v-model="value"
:class="props.inputClass"
type="url"
:placeholder="props.placeholder"
:disabled="props.disabled"
disable-focus-effect
/>
</template>
</ConfigField>
</template>

View File

@ -1,9 +1,9 @@
import type { KnipConfig } from "knip"
import { parse } from "vue/compiler-sfc"
// knip's nuxt plugin only knows the nuxt 3 layout, so the nuxt 4 app/
// conventions (and everything else referenced by convention rather than by
// import) are declared as entries here explicitly.
// knip's nuxt plugin handles the nuxt 4 app/ layout natively (it detects the
// app/ srcDir) and resolves auto-imported components and named composables
// through the generated .nuxt/imports.d.ts and .nuxt/components.d.ts maps —
// so only the conventions it cannot know about are configured here.
const config: KnipConfig = {
// the vendored PromQL grammar fork is re-synced from upstream and must
// not be reported against (see packages/README.lezer-promql.md)
@ -17,56 +17,16 @@ const config: KnipConfig = {
unlisted: "off",
},
compilers: {
// the SFC compiler recommended by knip's docs, extended to follow
// tailwind v4 @reference/@plugin directives in <style> blocks
vue: (text: string, filename: string) => {
const { descriptor } = parse(text, { filename, sourceMap: false })
const scripts = [descriptor.script, descriptor.scriptSetup]
.filter((block) => block !== null)
.map((block) => (block.src ? `import '${block.src}'` : block.content))
const styles = descriptor.styles.flatMap((block) => [
...[...block.content.matchAll(/(?<=@)import[^;]+/g)].map(String),
...[
...block.content.matchAll(/@(?:plugin|reference)\s+"([^"]+)"/g),
].map((m) => `import "${m[1]}"`),
])
return [...scripts, ...styles].join("\n")
},
// follows @import chains and tailwind v4 @plugin directives in the
// css entries nuxt loads (main.css)
css: (text: string) =>
[
...[...text.matchAll(/(?<=@)import[^;]+/g)].map(String),
...[...text.matchAll(/@plugin\s+"([^"]+)"/g)].map(
(m) => `import "${m[1]}"`,
),
].join("\n"),
},
workspaces: {
".": {
entry: [
// nuxt 4 app/ conventions: loaded by the framework, never
// imported. Components are included because templates
// reference them by tag through nuxt auto-registration,
// which knip cannot resolve — a component no longer used by
// any template is therefore NOT reported by knip.
"app/pages/**/*.vue",
"app/middleware/**/*.ts",
"app/plugins/**/*.ts",
"app/components/**/*.vue",
// the whole auto-import surface: unimport makes everything
// in these directories reachable without import statements,
// so knip cannot trace their usage. The tradeoff: unused
// exports inside these directories are NOT reported.
// the auto-import map connects named exports, but not the
// `export default function useX` style these composables
// use — without this their defaults are reported unused.
// The api/ barrel (index.ts) is also convention-load-bearing:
// it is what makes the nested api/ composables auto-importable.
"app/composables/**/*.ts",
"app/stores/**/*.ts",
"app/utils/**/*.ts",
// module conventions loaded by name
// module convention loaded by name (@pinia/colada-nuxt)
"colada.options.ts",
// electron main/preload world, built by forge + vite
"electron/main.ts",
@ -75,11 +35,6 @@ const config: KnipConfig = {
"vite.electron.config.ts",
],
// auto-imported by unimport (no import statements at use
// sites), so the files must count as project members even when
// nothing imports them
project: ["app/**/*.{ts,vue,css}", "electron/**/*.ts", "*.ts"],
ignore: [
// vendored shadcn-vue components: installed ahead of use and
// regenerated by the CLI; unused spares and barrel exports
@ -91,6 +46,9 @@ const config: KnipConfig = {
// icon collections resolved by name at build time by
// @nuxt/icon
"@iconify-json/.*",
// consumed only by the vendored shadcn drawer spares, which
// are ignored above
"vaul-vue",
// never imported directly — declared as the umbrella that
// provides the individually-imported @tiptap/extension-*
// packages (see rules.unlisted above)