//eslint-plugin-harlanzwbyharlan-zw

eslint-plugin-harlanzw

My opinionated ESLint rules for Vue apps

25
0
25
TypeScript

eslint-plugin-harlanzw

npm version
npm downloads
License

Harlan’s ESLint rules for Vue projects with focus on link hygiene, Nuxt best practices, and Vue reactivity patterns.

Made possible by my Sponsor Program 💖
Follow me @harlan_zw 🐦

Playground

Try the rules in action with a Nuxt ESLint interactive playground:

Open in StackBlitz

Rules

undefinedNote: These rules are experimental and may change. They will be submitted to the official Vue ESLint plugin for consideration.

The rules are organized into the following categories:

  • undefinedLink Rules - Ensure link URLs are clean, accessible, and SEO-friendly
  • undefinedNuxt Rules - Best practices for Nuxt applications
  • undefinedVue Rules - Vue composition API and reactivity best practices
  • undefinedAI Deslop Rules - Clean AI-generated slop from content markdown
Rule Description
undefinedLinkundefined
link-ascii-only ensure link URLs contain only ASCII characters
link-lowercase ensure link URLs do not contain uppercase characters
link-no-double-slashes ensure link URLs do not contain consecutive slashes
link-no-underscores ensure link URLs do not contain underscores
link-no-whitespace ensure link URLs do not contain whitespace characters
link-require-descriptive-text require descriptive link text
link-require-href require href/to attribute on link elements
link-trailing-slash enforce trailing slash consistency on link URLs
undefinedNuxtundefined
nuxt-await-navigate-to enforce awaiting navigateTo() calls
nuxt-no-redundant-import-meta disallow redundant import.meta.server or import.meta.client checks in scoped components
nuxt-no-side-effects-in-async-data-handler disallow side effects in async data handlers
nuxt-no-side-effects-in-setup disallow side effects in setup functions
nuxt-prefer-navigate-to-over-router-push-replace prefer navigateTo() over router.push() or router.replace()
nuxt-prefer-nuxt-link-over-router-link prefer NuxtLink over RouterLink
nuxt-ui-prefer-shorthand-css prefer Nuxt UI shorthand CSS classes over verbose var() syntax
undefinedVueundefined
vue-no-faux-composables stop fake composables that don’t use Vue reactivity
vue-no-nested-reactivity don’t mix ref() and reactive() together
vue-no-passing-refs-as-props don’t pass refs as props - unwrap them first
vue-no-reactive-destructuring avoid destructuring reactive objects
vue-no-ref-access-in-templates don’t use .value in Vue templates
vue-no-torefs-on-props don’t use toRefs() on the props object
vue-no-reactivity-after-await disallow subscription APIs (watch, computed, etc.) after await in async functions
vue-no-async-lifecycle-hook disallow async callbacks in Vue lifecycle hooks
vue-no-resolve-component-in-composables disallow resolveComponent()/resolveDirective() outside top-level <script setup>
vue-no-unresolvable-define-emits disallow unresolvable types in defineEmits type parameters
vue-prefer-define-emits-object-syntax prefer Vue 3.3+ object syntax for defineEmits over call signatures
vue-require-composable-prefix enforce use* prefix for functions using Vue reactivity
undefinedGeneralundefined
no-silent-catch disallow silently swallowing errors in .catch() or try/catch
undefinedAI Deslopundefined
ai-deslop-adverbs remove unnecessary adverbs that add no meaning (e.g. “significantly”, “fundamentally”)
ai-deslop-autolink auto-link first mention of known tech terms to their canonical URLs
ai-deslop-buzzwords replace AI-generated buzzword phrases with simpler alternatives (e.g. “leverage” → “use”)
ai-deslop-casing enforce correct casing for tech terms, brands, and abbreviations (e.g. “github” → “GitHub”)
ai-deslop-false-dichotomy flag “it’s not X, it’s Y” contrast patterns common in AI writing
ai-deslop-filler remove AI-generated filler sentences and phrases (e.g. “it’s worth noting that”)
ai-deslop-hedging remove hedging/qualifying words that weaken copy (e.g. “very”, “really”, “quite”, “just”)
ai-deslop-no-exclamation remove exclamation marks from content prose
ai-deslop-passive-voice flag passive voice constructions (e.g. “is generated” → rewrite in active voice)
ai-deslop-weak-opener flag weak sentence openers like “There is” and “It is possible to”
ai-deslop-frontmatter-spacing remove empty lines inside YAML frontmatter
ai-deslop-vue-ts-lang require lang="ts" on Vue <script> blocks in code examples
undefinedpnpmundefined
pnpm-require-trust-policy require trustPolicyIgnoreAfter: 262800 in pnpm-workspace.yaml

The plugin also includes 21 prompt linting rules for .prompt.md and .skill.md files. See the prompt configs section below.

Installation

Install the plugin:

pnpm add -D eslint-plugin-harlanzw

Usage

// eslint.config.js
import harlanzw from 'eslint-plugin-harlanzw'

export default harlanzw({
  link: true,
  nuxt: true,
  vue: true,
})

All link rules share ignoreExternal and exclude options. Configure them once:

export default harlanzw({
  link: {
    ignoreExternal: true, // skip http(s):// URLs and elements with `external` attr
    exclude: ['^/api/', '/OAuth/'], // skip URLs matching any regex pattern
    requireTrailingSlash: true, // passed to link-trailing-slash
  },
  nuxt: true,
  vue: true,
})

Extra Configs

Pass additional flat configs as extra arguments:

export default harlanzw(
  { link: true, nuxt: true, vue: true },
  {
    rules: {
      'harlanzw/link-lowercase': ['error', { ignoreExternal: true }],
    },
  },
)

With @antfu/eslint-config

import antfu from '@antfu/eslint-config'
import harlanzw from 'eslint-plugin-harlanzw'

export default antfu(
  { vue: true },
  ...harlanzw({ link: true, nuxt: true, vue: true }),
)

With Nuxt ESLint

import harlanzw from 'eslint-plugin-harlanzw'
import withNuxt from './.nuxt/eslint.config.mjs'

export default withNuxt(
  ...harlanzw({ link: true, nuxt: true, vue: true }),
)

Plugin Access

For custom configs or if you need the raw ESLint plugin object:

import { plugin } from 'eslint-plugin-harlanzw'

export default [
  ...plugin.configs.recommended, // link + nuxt + vue
  // or pick:
  ...plugin.configs.link,
  ...plugin.configs.nuxt,
  ...plugin.configs.vue,
]

AI Deslop Rules

12 rules for cleaning AI-generated slop from your content markdown files (content/**/*.md). Most rules are auto-fixable.

// eslint.config.js
export default harlanzw({
  content: true,
})

Or use the raw config:

import { plugin } from 'eslint-plugin-harlanzw'

export default [
  ...plugin.configs.content,
]
Rule What it does
ai-deslop-buzzwords Replaces overused AI phrases with plain alternatives (“leverage” → “use”, “delve into” → “explore”)
ai-deslop-filler Removes filler phrases that add nothing (“it’s worth noting that”, “at the end of the day”)
ai-deslop-adverbs Strips unnecessary adverbs (“significantly”, “fundamentally”, “essentially”)
ai-deslop-casing Fixes tech term casing using a 300+ term dictionary (“github” → “GitHub”, “typescript” → “TypeScript”)
ai-deslop-autolink Links first mention of tech terms to their canonical URLs (“Nuxt” → [Nuxt](https://nuxt.com))
ai-deslop-false-dichotomy Flags “it’s not X, it’s Y” false contrast patterns
ai-deslop-hedging Strips hedging words that weaken copy (“very”, “really”, “quite”, “just”, “somewhat”)
ai-deslop-no-exclamation Replaces exclamation marks with periods in content prose
ai-deslop-passive-voice Flags passive voice (“is generated”, “was created”) for active rewriting
ai-deslop-weak-opener Flags weak expletive openers (“There is”, “It is possible to”)
ai-deslop-frontmatter-spacing Removes empty lines inside YAML frontmatter blocks
ai-deslop-vue-ts-lang Adds lang="ts" to Vue <script> blocks in code examples

Prompt Rules

21 rules for linting .prompt.md and .skill.md files using a custom prompt language:

import { plugin } from 'eslint-plugin-harlanzw'

export default [
  ...plugin.configs['prompt:recommended'],
  // or stricter:
  // ...plugin.configs['prompt:strict'],
  // for skill files:
  // ...plugin.configs['prompt:skill'],
]

pnpm Rules

Enforces required fields in pnpm-workspace.yaml. Auto-enabled when the file exists.

export default harlanzw({
  pnpm: true,
})

Or use the raw config:

import { plugin } from 'eslint-plugin-harlanzw'

export default [
  ...plugin.configs.pnpm,
]
Rule What it does
pnpm-require-trust-policy Ensures trustPolicyIgnoreAfter: 262800 is present in pnpm-workspace.yaml (auto-fixable)

Sponsors

Sponsors

Credits

This plugin is based on eslint-plugin-antfu by Anthony Fu.

License

Licensed under the MIT license.

[beta]v0.14.0