Skip to content

Commit

Permalink
feat: eslint-plugin (#2140)
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Feb 2, 2023
1 parent da06c65 commit 72b1fdc
Show file tree
Hide file tree
Showing 28 changed files with 636 additions and 162 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Expand Up @@ -5,3 +5,4 @@ interactive/guides/vendor/*.md
interactive/data/guides.ts
defaultConfig.ts
packages/preset-icons/src/collections.json
packages/eslint-plugin/fixtures
61 changes: 56 additions & 5 deletions packages/core/src/utils/variantGroup.ts
@@ -1,4 +1,5 @@
import type MagicString from 'magic-string'
import { notNull } from '../utils'

const regexCache: Record<string, RegExp> = {}

Expand All @@ -10,19 +11,22 @@ export function makeRegexClassGroup(separators = ['-', ':']) {
return regexCache[key]
}

export function expandVariantGroup(str: string, separators?: string[], depth?: number): string
export function expandVariantGroup(str: MagicString, separators?: string[], depth?: number): MagicString
export function expandVariantGroup(str: string | MagicString, separators = ['-', ':'], depth = 5) {
export function parseVariantGroup(str: string, separators = ['-', ':'], depth = 5) {
const regexClassGroup = makeRegexClassGroup(separators)
let hasChanged = false
let content = str.toString()
const prefixes = new Set<string>()

do {
const before = content
content = content.replace(
regexClassGroup,
(from, pre, sep, body: string) => {
if (!separators.includes(sep))
return from

prefixes.add(pre + sep)

return body
.split(/\s/g)
.filter(Boolean)
Expand All @@ -34,12 +38,59 @@ export function expandVariantGroup(str: string | MagicString, separators = ['-',
depth -= 1
} while (hasChanged && depth)

return {
prefixes: Array.from(prefixes),
expanded: content,
hasChanged,
}
}

export function collapseVariantGroup(str: string, prefixes: string[]): string {
const collection = new Map<string, string[]>()

const sortedPrefix = prefixes.sort((a, b) => b.length - a.length)

return str.split(/\s+/g).map((part) => {
const prefix = sortedPrefix.find(prefix => part.startsWith(prefix))
if (!prefix)
return part

const body = part.slice(prefix.length)
if (collection.has(prefix)) {
collection.get(prefix)!.push(body)
return null
}
else {
const items = [body]
collection.set(prefix, items)
return {
prefix,
items,
}
}
})
.filter(notNull)
.map((i) => {
if (typeof i === 'string')
return i
return `${i.prefix}(${i.items.join(' ')})`
})
.join(' ')
}

export function expandVariantGroup(str: string, separators?: string[], depth?: number): string
export function expandVariantGroup(str: MagicString, separators?: string[], depth?: number): MagicString
export function expandVariantGroup(str: string | MagicString, separators = ['-', ':'], depth = 5) {
const {
expanded,
} = parseVariantGroup(str.toString(), separators, depth)

if (typeof str === 'string') {
return content
return expanded
}
else {
return str.length()
? str.overwrite(0, str.length(), content)
? str.overwrite(0, str.length(), expanded)
: str
}
}
29 changes: 29 additions & 0 deletions packages/eslint-config/README.md
@@ -0,0 +1,29 @@
# @unocss/eslint-config

ESLint config for UnoCSS.

**Currently working in progress, breaking changes may NOT follow semver.**

## Installation

```bash
npm i -D @unocss/eslint-config
```

In `.eslintrc`:

```json
{
"extends": [
"@unocss"
]
}
```

## Rules

- `@unocss/order` - Enforce a specific order for class and attribute selectors.

## Prior Arts

Thanks to [eslint-plugin-unocss](https://github.com/devunt/eslint-plugin-unocss) by [@devunt](https://github.com/devunt).
12 changes: 12 additions & 0 deletions packages/eslint-config/build.config.ts
@@ -0,0 +1,12 @@
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
entries: [
'src/index',
],
clean: true,
declaration: true,
rollup: {
emitCJS: true,
},
})
42 changes: 42 additions & 0 deletions packages/eslint-config/package.json
@@ -0,0 +1,42 @@
{
"name": "@unocss/eslint-config",
"version": "0.0.0",
"description": "ESLint config for UnoCSS",
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
"license": "MIT",
"funding": "https://github.com/sponsors/antfu",
"homepage": "https://github.com/unocss/unocss/tree/main/packages/eslint-config#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/unocss/unocss.git",
"directory": "packages/eslint-config"
},
"bugs": {
"url": "https://github.com/unocss/unocss/issues"
},
"keywords": ["eslint-config", "eslint"],
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.cjs",
"import": "./dist/index.mjs"
}
},
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"engines": {
"node": ">=14"
},
"scripts": {
"build": "unbuild",
"stub": "unbuild --stub"
},
"dependencies": {
"@unocss/eslint-plugin": "workspace:*"
}
}
5 changes: 5 additions & 0 deletions packages/eslint-config/src/index.ts
@@ -0,0 +1,5 @@
export default {
extends: [
'plugin:@unocss/recommended',
],
}
9 changes: 9 additions & 0 deletions packages/eslint-plugin/README.md
@@ -0,0 +1,9 @@
# @unocss/eslint-plugin

ESLint plugin for UnoCSS.

**Currently working in progress, breaking changes may NOT follow semver.**

## Installation

Please refer to [@unocss/eslint-config](../eslint-config/) for installation.
14 changes: 14 additions & 0 deletions packages/eslint-plugin/build.config.ts
@@ -0,0 +1,14 @@
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
entries: [
'src/dirs',
'src/index',
'src/worker-sort',
],
clean: true,
declaration: true,
rollup: {
emitCJS: true,
},
})
1 change: 1 addition & 0 deletions packages/eslint-plugin/fixtures/.eslintignore
@@ -0,0 +1 @@
.eslintrc.json
6 changes: 6 additions & 0 deletions packages/eslint-plugin/fixtures/.eslintrc.json
@@ -0,0 +1,6 @@
{
"extends": [
"@antfu",
"plugin:@unocss/recommended"
]
}
23 changes: 23 additions & 0 deletions packages/eslint-plugin/fixtures/src/App.vue
@@ -0,0 +1,23 @@
<template>
<div flex="~ gap-1 cols">
<div class="hover:text-red text-4xl mx1 m2">
<div i-carbon-campsite inline-block />
</div>
<p>
<a rel="noreferrer" href="https://github.com/antfu/vitesse" target="_blank">
Vitesse
</a>
</p>
<p>
<em text-sm opacity-75 class="foo" hover:text-red text-4xl mx1 m2>{{ t('intro.desc') }}</em>
</p>

<div class="hover:(text-red text-4xl) hover:mx1 mx1 m2">Variant group</div>

<div class="
foo bar
hover:(text-red text-4xl)
hover:mx1 mx1 m2
"></div>
</div>
</template>
7 changes: 7 additions & 0 deletions packages/eslint-plugin/fixtures/src/app.tsx
@@ -0,0 +1,7 @@
export default function App() {
return (
<h1 className="text-3xl hover:text-red m1 font-bold underline">
Hello world!
</h1>
)
}
25 changes: 25 additions & 0 deletions packages/eslint-plugin/fixtures/tsconfig.json
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
},
"include": [
"src"
]
}
11 changes: 11 additions & 0 deletions packages/eslint-plugin/fixtures/uno.config.ts
@@ -0,0 +1,11 @@
// eslint-disable-next-line no-restricted-imports
import { defineConfig, presetUno, transformerVariantGroup } from 'unocss'

export default defineConfig({
presets: [
presetUno(),
],
transformers: [
transformerVariantGroup(),
],
})
52 changes: 52 additions & 0 deletions packages/eslint-plugin/package.json
@@ -0,0 +1,52 @@
{
"name": "@unocss/eslint-plugin",
"version": "0.0.0",
"description": "ESLint plugin for UnoCSS",
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
"license": "MIT",
"funding": "https://github.com/sponsors/antfu",
"homepage": "https://github.com/unocss/unocss/tree/main/packages/esling-plugin#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/unocss/unocss.git",
"directory": "packages/esling-plugin"
},
"bugs": {
"url": "https://github.com/unocss/unocss/issues"
},
"keywords": [
"eslint-plugin",
"eslint"
],
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.cjs",
"import": "./dist/index.mjs"
}
},
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"engines": {
"node": ">=14"
},
"scripts": {
"build": "unbuild",
"lint": "nr build && cd ./fixtures && eslint ./src"
},
"dependencies": {
"@typescript-eslint/utils": "^5.50.0",
"@unocss/config": "workspace:^0.49.2",
"@unocss/core": "workspace:*",
"magic-string": "^0.27.0",
"synckit": "^0.8.5"
},
"devDependencies": {
"@unocss/eslint-plugin": "workspace:*"
}
}
7 changes: 7 additions & 0 deletions packages/eslint-plugin/src/configs/recommended.ts
@@ -0,0 +1,7 @@
export default {
plugins: ['@unocss'],
rules: {
'@unocss/order': 'warn',
'@unocss/order-attributify': 'warn',
},
}
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/constants.ts
@@ -0,0 +1 @@
export const CLASS_FIELDS = ['class', 'classname']
3 changes: 3 additions & 0 deletions packages/eslint-plugin/src/dirs.ts
@@ -0,0 +1,3 @@
import { fileURLToPath } from 'url'

export const distDir = fileURLToPath(new URL('../dist', import.meta.url))
13 changes: 13 additions & 0 deletions packages/eslint-plugin/src/index.ts
@@ -0,0 +1,13 @@
import order from './rules/order'
import orderAttributify from './rules/order-attributify'
import configsRecommended from './configs/recommended'

export default {
rules: {
order,
'order-attributify': orderAttributify,
},
configs: {
recommended: configsRecommended,
},
}

0 comments on commit 72b1fdc

Please sign in to comment.