Skip to content

Commit

Permalink
feat(ui/eslint): add eslint plugin for artalk ui development
Browse files Browse the repository at this point in the history
- Implemented `eslint-plugin-artalk` to enforce Artalk's development conventions.
- Added setup script for UI development environment.
- Created test cases for Artalk plugin rules.
- Configured TypeScript and build settings for the plugin.
- Updated documentation with installation and configuration instructions.
  • Loading branch information
qwqcode committed Oct 5, 2024
1 parent db960aa commit 28466c4
Show file tree
Hide file tree
Showing 10 changed files with 577 additions and 0 deletions.
29 changes: 29 additions & 0 deletions scripts/setup-ui-dev-env.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { exec } from 'child_process'

function runCommand(command) {
return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if (error) {
reject(stderr || stdout)
} else {
resolve(stdout)
}
})
})
}

async function build() {
try {
// Build Artalk Plugin Kit for plugin development
await runCommand('pnpm build:plugin-kit')
const green = '\x1b[32m'
console.log(green, '[ArtalkDev] build @artalk/plugin-kit success')
// Build Artalk eslint plugin for lint checking
await runCommand('pnpm build:eslint-plugin')
console.log(green, '[ArtalkDev] build eslint-plugin-artalk success')
} catch (error) {
console.error('[ArtalkDev] Artalk UI development environment setup failed:', error)
}
}

build()
121 changes: 121 additions & 0 deletions ui/eslint-plugin-artalk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# eslint-plugin-artalk [![npm](https://img.shields.io/npm/v/eslint-plugin-artalk)](https://www.npmjs.com/package/eslint-plugin-artalk)

The ESLint plugin enforcing Artalk's development conventions.

It is a part of the [Plugin Development Kit](https://artalk.js.org/develop/plugin.html) for Artalk.

## Installation

```bash
pnpm add -D eslint-plugin-artalk
```

Since Artalk development is based on TypeScript and the plugin relies on it, you need to install `typescript` and `@typescript-eslint/parser`. For more details, refer to [TypeScript ESLint](https://typescript-eslint.io/getting-started/).

### Flat Configuration

Modify the `eslint.config.mjs` file in your project:

<!-- prettier-ignore -->
```js
import eslintJs from '@eslint/js'
import eslintTs from 'typescript-eslint'
import pluginArtalk from 'eslint-plugin-artalk'

export default eslintTs.config(
eslintJs.configs.recommended,
...eslintTs.configs.recommended,
{
files: ['**/*.{ts,mts,cts,tsx,js,mjs,cjs}'],
languageOptions: {
parser: eslintTs.parser,
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
},
plugins: {
artalk: pluginArtalk,
},
rules: {
...pluginArtalk.configs.recommended.rules,
},
}
)
```

<!-- prettier-ignore-end -->

### Custom Configuration

You can customize the rules by modifying the `rules` field in the configuration:

```js
{
plugins: {
artalk: pluginArtalk,
},
rules: {
'artalk/artalk-plugin': 'error',
},
}
```

## Valid and Invalid Examples

### Rule `artalk-plugin`

The ESLint rule `artalk/artalk-plugin` enforces the conventions for Artalk plugins.

The ESLint rule is only enabled when a TypeScript file imports the `ArtalkPlugin` type from the `artalk` package and defines an arrow function variable with the type `ArtalkPlugin`, such as `const TestPlugin: ArtalkPlugin = (ctx) => {}`. The variable type must be `ArtalkPlugin`.

#### `noLifeCycleEventInNestedBlocks`

Should not allow life-cycle event listeners to be defined inside nested blocks.

The life-cycle event listeners are `created`, `mounted`, `updated`, and `destroyed` must be defined in the top-level scope of the ArtalkPlugin arrow function.

**⚠️ Fail**:

```ts
import type { ArtalkPlugin } from 'artalk'

export const TestPlugin: ArtalkPlugin = (ctx) => {
const foo = () => {
const bar = () => {
ctx.on('updated', () => {})
}
}
}
```

**✅ Pass**:

```ts
import type { ArtalkPlugin } from 'artalk'

export const TestPlugin: ArtalkPlugin = (ctx) => {
ctx.on('updated', () => {})
}
```

#### `noEventInWatchConf`

Should not allow event listeners to be defined inside watchConf effect function.

**⚠️ Fail**:

```ts
import type { ArtalkPlugin } from 'artalk'

export const TestPlugin: ArtalkPlugin = (ctx) => {
ctx.watchConf(['el'], (conf) => {
ctx.on('update', () => {})
})
}
```

## License

[MIT](https://github.com/ArtalkJS/Artalk/blob/master/LICENSE)
38 changes: 38 additions & 0 deletions ui/eslint-plugin-artalk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "eslint-plugin-artalk",
"version": "1.0.0",
"type": "module",
"license": "MIT",
"homepage": "https://github.com/ArtalkJS/Artalk/tree/master/ui/eslint-plugin-artalk",
"files": [
"dist",
"README.md"
],
"main": "dist/main.js",
"types": "dist/main.d.ts",
"exports": {
".": {
"require": {
"types": "./dist/main.d.cjs",
"default": "./dist/main.cjs"
},
"default": {
"types": "./dist/main.d.ts",
"default": "./dist/main.js"
}
}
},
"scripts": {
"dev": "tsup --watch",
"build": "tsup",
"test": "vitest"
},
"devDependencies": {
"@typescript-eslint/rule-tester": "^8.8.0",
"tsup": "^8.3.0"
},
"peerDependencies": {
"@typescript-eslint/utils": ">=8",
"eslint": ">=9"
}
}
42 changes: 42 additions & 0 deletions ui/eslint-plugin-artalk/src/artalk-plugin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { artalkPlugin } from './artalk-plugin'
import { setupTest } from './test-helper'

const { ruleTester } = setupTest()

const invalid = [
{
name: 'should not allow life-cycle event functions in nested blocks',
code: `
import type { ArtalkPlugin } from 'artalk'
export const TestPlugin: ArtalkPlugin = (ctx) => {
const foo = () => {
const bar = () => {
ctx.on('updated', () => {})
}
}
}
`,
errorId: 'noLifeCycleEventInNestedBlocks',
},
{
name: "should not allow 'off' call inside watchConf effect",
code: `
import type { ArtalkPlugin } from 'artalk'
export const TestPlugin: ArtalkPlugin = (ctx) => {
ctx.watchConf(['pageVote'], (conf) => {
ctx.off('updated', () => {})
})
}
`,
errorId: 'noEventInWatchConf',
},
]

for (const { name, code, errorId } of invalid) {
ruleTester.run(name, artalkPlugin as any, {
valid: [],
invalid: [{ code, errors: [{ messageId: errorId }] }],
})
}
Loading

0 comments on commit 28466c4

Please sign in to comment.