Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
73 changes: 24 additions & 49 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import js from '@eslint/js'
import vue from 'eslint-plugin-vue'
import globals from 'globals'
import typescript from '@typescript-eslint/eslint-plugin'
import typescriptParser from '@typescript-eslint/parser'
import vueParser from 'vue-eslint-parser'
Expand All @@ -22,44 +23,8 @@ export default [
sourceType: 'module'
},
globals: {
// Browser globals
window: 'readonly',
document: 'readonly',
navigator: 'readonly',
console: 'readonly',
fetch: 'readonly',
localStorage: 'readonly',
setTimeout: 'readonly',
clearTimeout: 'readonly',
setInterval: 'readonly',
clearInterval: 'readonly',
ResizeObserver: 'readonly',
requestAnimationFrame: 'readonly',
cancelAnimationFrame: 'readonly',
Element: 'readonly',
SVGElement: 'readonly',
HTMLElement: 'readonly',
HTMLAnchorElement: 'readonly',
HTMLButtonElement: 'readonly',
HTMLSelectElement: 'readonly',
HTMLInputElement: 'readonly',
HTMLOptionElement: 'readonly',
HTMLTextAreaElement: 'readonly',
HTMLLabelElement: 'readonly',
MouseEvent: 'readonly',
KeyboardEvent: 'readonly',
Event: 'readonly',
Node: 'readonly',
// Node globals
process: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
require: 'readonly',
module: 'readonly',
// ES2022 globals
Promise: 'readonly',
Symbol: 'readonly',
Reflect: 'readonly'
...globals.browser,
...globals.node
}
},
plugins: {
Expand All @@ -79,7 +44,7 @@ export default [
'vue/component-definition-name-casing': ['error', 'PascalCase'],
'vue/component-name-in-template-casing': ['error', 'PascalCase'],
'vue/component-tags-order': ['error', { order: ['script[setup]', 'template', 'style'] }],
'vue/multi-word-component-names': 'off', // Allow flexibility in component naming
'vue/multi-word-component-names': 'off',
'vue/no-arrow-functions-in-watch': 'error',
'vue/no-async-in-computed-properties': 'error',
'vue/no-child-content': 'error',
Expand All @@ -101,11 +66,11 @@ export default [
shallowOnly: false
}
],
// "vue/no-reserved-component-names": ['error', {
// "disallowVueBuiltInComponents": false,
// "disallowVue3BuiltInComponents": false,
// "htmlElementCaseSensitive": false,
// }],
"vue/no-reserved-component-names": ['error', {
"disallowVueBuiltInComponents": true,
"disallowVue3BuiltInComponents": true,
"htmlElementCaseSensitive": true,
}],
'vue/no-restricted-syntax': 'error',
'vue/no-reserved-keys': [
'error',
Expand All @@ -117,14 +82,14 @@ export default [
'vue/no-reserved-props': [
'error',
{
vueVersion: 3 // or 2
vueVersion: 3
}
],
'vue/no-unused-vars': 'error',
'vue/v-if-else-key': 'error',
'vue/no-ref-as-operand': 'error',
'vue/no-side-effects-in-computed-properties': 'error',
'vue/no-v-html': 'error', // Security
'vue/no-v-html': 'error',
'vue/require-default-prop': 'error',
'vue/require-explicit-emits': 'error',
'vue/template-curly-spacing': 'error',
Expand All @@ -136,10 +101,20 @@ export default [
'vuejs-accessibility/click-events-have-key-events': 'error',

// TypeScript
'no-unused-vars': 'off', // 'error',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'off',
// '@typescript-eslint/explicit-module-boundary-types': 'error',
'@typescript-eslint/no-explicit-any': 'error',
'unused-imports/no-unused-imports': 'error',
'unused-imports/no-unused-vars': [
'error',
{
vars: 'all',
varsIgnorePattern: '^_',
args: 'after-used',
argsIgnorePattern: '^_'
}
],

// Import organization
'simple-import-sort/imports': 'error',
Expand All @@ -149,7 +124,7 @@ export default [
'import/no-duplicates': 'error',

// Clean code
'no-console': ['error', { allow: ['warn', 'error'] }], // Prevent console.log in production code
'no-console': ['error', { allow: ['warn', 'error'] }],
'no-debugger': 'error',
'prefer-const': 'error'
},
Expand Down
6 changes: 3 additions & 3 deletions lint-canaries/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ lint-canaries/
eslint/
vue-correctness/ # DOC_LINTS §2 — one .vue per vue/* rule
accessibility/ # §3 — vuejs-accessibility/*
typescript/ # §4 — @typescript-eslint/*
import-hygiene/ # §5 — simple-import-sort/*, import/*
typescript/ # §4 — @typescript-eslint/*, unused-imports/no-unused-vars
import-hygiene/ # §5 — simple-import-sort/*, import/*, unused-imports/no-unused-imports
clean-code/ # §6 — no-console, no-debugger, prefer-const
stylelint/ # §7 — one .css per rule
prettier/ # §8 — files that must stay "unformatted"
commitlint/ # §10 — one wrong commit message per .txt
```

54 canaries: 37 ESLint · 8 Stylelint · 2 Prettier · 7 commitlint.
56 canaries: 39 ESLint · 8 Stylelint · 2 Prettier · 7 commitlint.

## Why these files never break the normal pipeline

Expand Down
6 changes: 6 additions & 0 deletions lint-canaries/eslint/import-hygiene/no-unused-imports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// CANARY — must keep failing unused-imports/no-unused-imports.
// `computed` is imported and never used — the one unused-code case
// `lint:fix` deletes automatically.
import { computed, ref } from 'vue'
Comment thread
robsongajunior marked this conversation as resolved.
Dismissed

export const count = ref(0)
14 changes: 7 additions & 7 deletions lint-canaries/eslint/typescript/no-unused-vars.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// CANARY — must keep failing @typescript-eslint/no-unused-vars.
// `computed` is imported and never used; `event` is an unused parameter
// without the `_` prefix that would mark it intentional.
import { computed, ref } from 'vue'

export function onSelect(event: MouseEvent, item: { label: string }) {
return ref(item.label)
// CANARY — must keep failing unused-imports/no-unused-vars.
// `orphan` is assigned and never used, without the `_` prefix that would
// mark it intentional. (Unused IMPORTS are guarded separately by
// eslint/import-hygiene/no-unused-imports.ts.)
export function onSelect(item: { label: string }) {
const orphan = item.label
Comment thread
robsongajunior marked this conversation as resolved.
Dismissed
return item
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script setup>
// CANARY — must keep failing vue/no-reserved-component-names.
// `Teleport` is a Vue 3 built-in component name (disallowVue3BuiltInComponents).
defineOptions({ name: 'Teleport' })
</script>

<template>
<div>reserved component name</div>
</template>
10 changes: 9 additions & 1 deletion lint-canaries/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ const ESLINT_CANARIES = [
{ file: 'eslint/vue-correctness/no-mutating-props.vue', rule: 'vue/no-mutating-props' },
{ file: 'eslint/vue-correctness/no-reserved-keys.vue', rule: 'vue/no-reserved-keys' },
{ file: 'eslint/vue-correctness/no-reserved-props.vue', rule: 'vue/no-reserved-props' },
{
file: 'eslint/vue-correctness/no-reserved-component-names.vue',
rule: 'vue/no-reserved-component-names'
},
{ file: 'eslint/vue-correctness/no-unused-vars.vue', rule: 'vue/no-unused-vars' },
{ file: 'eslint/vue-correctness/v-if-else-key.vue', rule: 'vue/v-if-else-key' },
{ file: 'eslint/vue-correctness/no-ref-as-operand.vue', rule: 'vue/no-ref-as-operand' },
Expand All @@ -98,14 +102,18 @@ const ESLINT_CANARIES = [
rule: 'vuejs-accessibility/click-events-have-key-events'
},
// ESLint — TypeScript
{ file: 'eslint/typescript/no-unused-vars.ts', rule: '@typescript-eslint/no-unused-vars' },
{ file: 'eslint/typescript/no-unused-vars.ts', rule: 'unused-imports/no-unused-vars' },
{ file: 'eslint/typescript/no-explicit-any.ts', rule: '@typescript-eslint/no-explicit-any' },
// ESLint — Import hygiene
{ file: 'eslint/import-hygiene/sorted-imports.ts', rule: 'simple-import-sort/imports' },
{ file: 'eslint/import-hygiene/sorted-exports.ts', rule: 'simple-import-sort/exports' },
{ file: 'eslint/import-hygiene/first.ts', rule: 'import/first' },
{ file: 'eslint/import-hygiene/newline-after-import.ts', rule: 'import/newline-after-import' },
{ file: 'eslint/import-hygiene/no-duplicates.ts', rule: 'import/no-duplicates' },
{
file: 'eslint/import-hygiene/no-unused-imports.ts',
rule: 'unused-imports/no-unused-imports'
},
// ESLint — Clean code
{ file: 'eslint/clean-code/no-console.ts', rule: 'no-console' },
{ file: 'eslint/clean-code/no-debugger.ts', rule: 'no-debugger' },
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"eslint-plugin-unused-imports": "^4.4.1",
"eslint-plugin-vue": "^9",
"eslint-plugin-vuejs-accessibility": "^2.5.0",
"globals": "^17.8.0",
"husky": "^9.1.7",
"lint-staged": "^16.4.0",
"postcss": "^8.5.10",
Expand Down
60 changes: 47 additions & 13 deletions packages/webkit/docs/DOC_LINTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,26 @@ export default {
</script>
```

### `vue/no-reserved-component-names` — no HTML / Vue built-in shadowing

A component cannot take a name Vue or HTML already owns: Vue 2/3 built-ins are banned outright (`disallowVueBuiltInComponents` / `disallowVue3BuiltInComponents`), and HTML element names are checked **case-sensitively** (`htmlElementCaseSensitive: true`) — deliberate, so the DS's PascalCase roots (`Table`, `Button`, `Link`) stay valid while a literal lowercase `table` is rejected.

**❌ Wrong**

```vue
<script setup lang="ts">
defineOptions({ name: 'Teleport' }) // shadows the Vue 3 built-in
</script>
```

**✅ Correct**

```vue
<script setup lang="ts">
defineOptions({ name: 'Table' }) // PascalCase — no case-sensitive collision
</script>
```

### `vue/no-unused-vars`

Unused `v-for` / scope variables are dead code in the template.
Expand Down Expand Up @@ -781,27 +801,24 @@ Anything clickable must be keyboard-operable.

## 4. ESLint — TypeScript

### `@typescript-eslint/no-unused-vars` (`argsIgnorePattern: '^_'`)
### `unused-imports/no-unused-vars` (`varsIgnorePattern: '^_'`, `argsIgnorePattern: '^_'`)

Dead imports, variables, and parameters are errors. An **intentionally** unused argument is spelled with a leading underscore. (Core `no-unused-vars` is off in favor of this TS-aware version.)
Dead variables and parameters are errors. An **intentionally** unused one is spelled with a leading underscore. (Core `no-unused-vars` and `@typescript-eslint/no-unused-vars` are both off in favor of the `unused-imports` pair, which splits unused **imports** into their own auto-fixable rule — §5.)

**❌ Wrong**

```ts
import { computed, ref } from 'vue' // computed never used

function onSelect(event: MouseEvent, item: Item) {
select(item) // event unused → error
function onSelect(item: Item, event: MouseEvent) {
const orphan = item.label // assigned, never used → error
return select(item) // trailing `event` unused → error
}
```

**✅ Correct**

```ts
import { ref } from 'vue'

function onSelect(_event: MouseEvent, item: Item) {
select(item)
function onSelect(item: Item, _event: MouseEvent) {
return select(item)
}
```

Expand Down Expand Up @@ -934,6 +951,26 @@ import { useAttrs } from 'vue'
import { computed, useAttrs } from 'vue'
```

### `unused-imports/no-unused-imports`

An import nothing uses is dead code — and the one unused-code case `lint:fix` deletes automatically (plain unused variables — §4 — only report; they are never auto-removed).

**❌ Wrong**

```ts
import { computed, ref } from 'vue' // computed never used

export const count = ref(0)
```

**✅ Correct**

```ts
import { ref } from 'vue'

export const count = ref(0)
```

---

## 6. ESLint — Clean code
Expand Down Expand Up @@ -1544,6 +1581,3 @@ A `.vue` whose props / events / slots / name / testid / animations diverge from
Honest edges of the config:

- **`vue/no-restricted-syntax`** is enabled with no selectors — it restricts nothing until selectors are added.
- **`vue/no-reserved-component-names`** is commented out in `eslint.config.js`.
- **`eslint-plugin-unused-imports`** is registered but none of its rules are enabled — unused imports are caught indirectly by `@typescript-eslint/no-unused-vars` (not auto-fixed on `lint:fix`).
- **`packages/webkit` `lint` script** passes `--ext .js,.js,.vue` (`.ts` missing, `.js` doubled) — plain `.ts` files are still covered by lint-staged and CI, which pass explicit extensions.
8 changes: 4 additions & 4 deletions packages/webkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
"scripts": {
"clean": "rm -rf dist/",
"pack:check": "! npm pack --dry-run 2>&1 | grep -E '\\.test\\.(ts|js)|src/test/'",
"format": "prettier --write \"**/*.{js,ts,vue,css,json,md}\"",
"format:check": "prettier --check \"**/*.{js,ts,vue,css,json,md}\"",
"lint": "eslint src --ext .js,.ts,.vue --max-warnings 0",
"lint:fix": "eslint src --ext .js,.ts,.vue --fix",
"format": "prettier --write \"**/*.{js,mjs,ts,vue,css,json,md}\"",
"format:check": "prettier --check \"**/*.{js,mjs,ts,vue,css,json,md}\"",
"lint": "eslint src --ext .js,.mjs,.ts,.vue --max-warnings 0",
"lint:fix": "eslint src --ext .js,.mjs,.ts,.vue --fix",
"lint:style": "stylelint \"src/**/*.{css,scss,vue}\"",
"lint:style:fix": "stylelint \"src/**/*.{css,scss,vue}\" --fix",
"type-check": "vue-tsc --noEmit",
Expand Down
5 changes: 2 additions & 3 deletions packages/webkit/test/mcp/queries.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,8 @@ test('listTokens surfaces the animations catalog with timing + use-when', () =>
})

test('every animate-* utility has a useWhen entry (guidance stays in sync with the catalog)', async () => {
const { animate, useWhen } = await import(
'../../../theme/src/tokens/primitives/animations/animate.js'
)
const { animate, useWhen } =
await import('../../../theme/src/tokens/primitives/animations/animate.js')
for (const name of Object.keys(animate)) {
assert.ok(
typeof useWhen[name] === 'string' && useWhen[name].length > 0,
Expand Down
15 changes: 10 additions & 5 deletions packages/webkit/test/standards/authoring-docs.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import assert from 'node:assert/strict'

import { scanDoc, docFileKind } from '../../../../.claude/hooks/_lib/authoring-docs-checks.mjs'

const ids = (rel, content) => scanDoc(rel, content).map((v) => v.id).sort()
const ids = (rel, content) =>
scanDoc(rel, content)
.map((v) => v.id)
.sort()

test('docFileKind resolves each population and skips non-governed paths', () => {
assert.deepEqual(docFileKind('.claude/skills/add-animation/SKILL.md'), {
Expand All @@ -27,10 +30,12 @@ test('docFileKind resolves each population and skips non-governed paths', () =>
docFileKind('packages/webkit/cli-templates/claude/skills/webkit-usage/SKILL.md'),
{ kind: 'skill', variant: 'consumer', scope: 'general', unit: 'webkit-usage' }
)
assert.deepEqual(
docFileKind('packages/webkit/cli-templates/claude/agents/webkit-expert.md'),
{ kind: 'agent', variant: 'consumer', scope: 'general', unit: 'webkit-expert' }
)
assert.deepEqual(docFileKind('packages/webkit/cli-templates/claude/agents/webkit-expert.md'), {
kind: 'agent',
variant: 'consumer',
scope: 'general',
unit: 'webkit-expert'
})
// not governed
assert.equal(docFileKind('.claude/agents/_README.md'), null)
assert.equal(docFileKind('.claude/skills/add-animation/references/foo.md'), null)
Expand Down
3 changes: 1 addition & 2 deletions packages/webkit/test/standards/invariant.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ const frontmatterOf = (content) => {
const m = content.match(/^---\n([\s\S]*?)\n---/)
return m ? m[1] : ''
}
const enforcedByOf = (rel) =>
parseEnforcedBy(frontmatterOf(readFileSync(join(ROOT, rel), 'utf-8')))
const enforcedByOf = (rel) => parseEnforcedBy(frontmatterOf(readFileSync(join(ROOT, rel), 'utf-8')))

test('every rule doc is registered as a standard, and every standard has a rule doc', () => {
const rules = new Set(ruleIds)
Expand Down
Loading
Loading