deslop 0.2.0

A static analyzer that spots low-context and AI-assisted code patterns across naming, concurrency, security, performance, and test quality.
Documentation
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'
import { ArrowUpRightIcon, Bars3Icon, MoonIcon, SunIcon, XMarkIcon } from '@heroicons/react/24/outline'
import { Link, useLocation, useNavigate } from 'react-router-dom'

import { navigation, siteMetadata } from '../../../content/site-content'
import { cn } from '../../../shared/lib/cn'
import { isHomePath, sitePath } from '../../../shared/lib/sitePath'
import { useGithubStars } from '../../../shared/lib/useGithubStars'
import type { Theme } from '../../../shared/lib/useTheme'
import { Container } from '../../../shared/ui/Container'
import { GitHubStarsBadge } from '../../../shared/ui/GitHubStarsBadge'

function Logo() {
  return (
    <Link to="/" className="flex items-center gap-3">
      <div className="flex h-11 w-11 items-center justify-center border-2 border-[var(--text)] bg-[var(--text)] font-['Newsreader'] italic text-2xl font-bold text-[var(--bg)]">
        <span className="translate-x-[-1px]">d/</span>
      </div>
      <span className="flex flex-col leading-none">
        <span className="font-['Newsreader'] italic text-2xl font-bold tracking-[-0.02em] text-[var(--text)]">deslop</span>
        <span className="text-[0.76rem] tracking-[0.02em] text-[var(--muted)] mt-1">
        A minimal tool to detect bad practices in AI-generated code
        </span>
      </span>
    </Link>
  )
}

const navLinkClassName =
  'px-4 py-2 text-sm font-medium text-[var(--muted)] transition hover:text-[var(--text)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--accent)]'

const navLinkActiveClassName =
  'px-4 py-2 text-sm font-medium text-[var(--text-strong)] transition hover:text-[var(--text)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--accent)]'

type HeaderProps = {
  theme: Theme
  onToggleTheme: () => void
}

type ThemeToggleButtonProps = HeaderProps & {
  compact?: boolean
}

function ThemeToggleButton({ theme, onToggleTheme, compact = false }: ThemeToggleButtonProps) {
  const nextTheme = theme === 'dark' ? 'light' : 'dark'
  const Icon = theme === 'dark' ? SunIcon : MoonIcon

  return (
    <button
      type="button"
      onClick={onToggleTheme}
      className={cn(
        'inline-flex min-h-11 items-center justify-center gap-2 border-2 border-[var(--border)] bg-transparent text-[var(--text)] transition hover:border-[var(--text)] hover:bg-[var(--text)] hover:text-[var(--bg)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--accent)]',
        compact ? 'h-11 w-11 px-0' : 'px-4',
      )}
      aria-label={`Switch to ${nextTheme} mode`}
      title={`Switch to ${nextTheme} mode`}
    >
      <Icon className="h-5 w-5" aria-hidden="true" />
      {!compact && <span className="hidden lg:inline">{nextTheme === 'light' ? 'Light mode' : 'Dark mode'}</span>}
    </button>
  )
}

export function Header({ theme, onToggleTheme }: HeaderProps) {
  const { stars, isLoading } = useGithubStars(siteMetadata.github.owner, siteMetadata.github.repo)
  const location = useLocation()
  const navigate = useNavigate()

  const handleInstallClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
    e.preventDefault()
    if (isHomePath(location.pathname)) {
      const el = document.getElementById('install-run')
      if (el) {
        el.scrollIntoView({ behavior: 'smooth' })
        window.history.pushState(null, '', sitePath('#install-run'))
      }
    } else {
      // If we are on /docs, navigate back to home
      navigate('/')
      // Small delay to allow react to render the homepage before scrolling natively
      setTimeout(() => {
        document.getElementById('install-run')?.scrollIntoView({ behavior: 'smooth' })
        window.history.pushState(null, '', sitePath('#install-run'))
      }, 100)
    }
  }

  return (
    <Disclosure as="header" className="sticky top-0 z-50 border-b border-[var(--border)] bg-[var(--header-bg)] backdrop-blur-xl">
      {({ open }) => (
        <>
          <Container className="flex items-center justify-between gap-4 py-4">
            <div className="flex items-center gap-3">
              <GitHubStarsBadge
                href={siteMetadata.github.url}
                stars={stars}
                isLoading={isLoading}
                className="hidden sm:inline-flex"
              />
              <Logo />
            </div>

            <nav className="hidden items-center gap-1 md:flex">
              {navigation.map((item) => (
                item.href.includes('#') ? (
                  <a key={item.href} href={sitePath(item.href)} className={navLinkClassName} onClick={handleInstallClick}>
                    {item.label}
                  </a>
                ) : (
                  <Link
                    key={item.href}
                    to={item.href}
                    className={location.pathname === item.href ? navLinkActiveClassName : navLinkClassName}
                  >
                    {item.label}
                  </Link>
                )
              ))}
            </nav>

            <div className="hidden items-center gap-3 md:flex">
              <ThemeToggleButton theme={theme} onToggleTheme={onToggleTheme} />
              <a 
                href={sitePath('#install-run')} 
                className="button-primary"
                onClick={handleInstallClick}
              >
                Install and run
                <ArrowUpRightIcon className="h-4 w-4" aria-hidden="true" />
              </a>
            </div>

            <div className="flex items-center gap-2 md:hidden">
              <ThemeToggleButton theme={theme} onToggleTheme={onToggleTheme} compact />
              <DisclosureButton
                className="flex h-11 w-11 items-center justify-center border-2 border-[var(--border)] bg-transparent text-[var(--text)] transition hover:border-[var(--text)] hover:bg-[var(--text)] hover:text-[var(--bg)]"
                aria-label={open ? 'Close navigation' : 'Open navigation'}
              >
                {open ? <XMarkIcon className="h-5 w-5" aria-hidden="true" /> : <Bars3Icon className="h-5 w-5" aria-hidden="true" />}
              </DisclosureButton>
            </div>
          </Container>

          <DisclosurePanel className="border-t border-[var(--border)] md:hidden">
            <Container className="pb-5">
              <div className="glass-panel p-3 border-x-0 border-b-2">
                <GitHubStarsBadge
                  href={siteMetadata.github.url}
                  stars={stars}
                  isLoading={isLoading}
                  className="mb-3 w-full justify-center"
                />
                <div className="flex flex-col gap-1">
                  {navigation.map((item) => (
                    item.href.includes('#') ? (
                      <a key={item.href} href={sitePath(item.href)} className={cn(navLinkClassName, 'px-4 py-3')} onClick={handleInstallClick}>
                        {item.label}
                      </a>
                    ) : (
                      <Link key={item.href} to={item.href} className={cn(navLinkClassName, 'px-4 py-3')}>
                        {item.label}
                      </Link>
                    )
                  ))}
                </div>
                <a 
                  href={sitePath('#install-run')} 
                  className="button-primary mt-3 w-full"
                  onClick={handleInstallClick}
                >
                  Install and run
                </a>
              </div>
            </Container>
          </DisclosurePanel>
        </>
      )}
    </Disclosure>
  )
}