Skip to content

Commit

Permalink
feat: add ESLint package @react-three/eslint-plugin (#2698)
Browse files Browse the repository at this point in the history
  • Loading branch information
itsdouges authored Jan 12, 2023
1 parent d43bb30 commit 1e0ca98
Show file tree
Hide file tree
Showing 14 changed files with 513 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .changeset/serious-lions-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@react-three/eslint-plugin': minor
---

Initial release.
10 changes: 8 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@
"es6": true,
"node": true
},
"extends": ["prettier", "plugin:prettier/recommended", "plugin:react-hooks/recommended", "plugin:import/recommended"],
"plugins": ["@typescript-eslint", "react", "react-hooks", "import", "jest", "prettier"],
"extends": [
"prettier",
"plugin:prettier/recommended",
"plugin:react-hooks/recommended",
"plugin:import/recommended",
"plugin:@react-three/recommended"
],
"plugins": ["@typescript-eslint", "react", "react-hooks", "import", "jest", "prettier", "@react-three"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
Expand Down
10 changes: 1 addition & 9 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,8 @@ module.exports = {
'<rootDir>/packages/test-renderer/dist',
'<rootDir>/test-utils',
],
// coverageThreshold: {
// global: {
// statements: 80,
// branches: 68,
// functions: 80,
// lines: 80,
// },
// },
coverageDirectory: './coverage/',
collectCoverage: true,
collectCoverage: false,
moduleFileExtensions: ['js', 'ts', 'tsx'],
verbose: false,
testTimeout: 30000,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"validate": "preconstruct validate",
"release": "yarn build && yarn changeset publish",
"vers": "yarn changeset version",
"codegen:eslint": "cd packages/eslint-plugin && yarn codegen",
"analyze-fiber": "cd packages/fiber && npm publish --dry-run",
"analyze-test": "cd packages/test-renderer && npm publish --dry-run"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/eslint-plugin/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
scripts/
src/
index.js
81 changes: 81 additions & 0 deletions packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<!--- THIS FILE WAS GENERATED DO NOT MODIFY BY HAND -->
<!--- @command yarn codegen:eslint -->

# @react-three/eslint-plugin

[![Version](https://img.shields.io/npm/v/@react-three/eslint-plugin?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/@react-three/eslint-plugin)
[![Twitter](https://img.shields.io/twitter/follow/pmndrs?label=%40pmndrs&style=flat&colorA=000000&colorB=000000&logo=twitter&logoColor=000000)](https://twitter.com/pmndrs)
[![Discord](https://img.shields.io/discord/740090768164651008?style=flat&colorA=000000&colorB=000000&label=discord&logo=discord&logoColor=000000)](https://discord.gg/ZZjjNvJ)
[![Open Collective](https://img.shields.io/opencollective/all/react-three-fiber?style=flat&colorA=000000&colorB=000000)](https://opencollective.com/react-three-fiber)
[![ETH](https://img.shields.io/badge/ETH-f5f5f5?style=flat&colorA=000000&colorB=000000)](https://blockchain.com/eth/address/0x6E3f79Ea1d0dcedeb33D3fC6c34d2B1f156F2682)
[![BTC](https://img.shields.io/badge/BTC-f5f5f5?style=flat&colorA=000000&colorB=000000)](https://blockchain.com/btc/address/36fuguTPxGCNnYZSRdgdh6Ea94brCAjMbH)

An ESLint plugin which provides lint rules for [@react-three/fiber](https://github.com/pmndrs/react-three-fiber).

## Installation

```bash
npm install @react-three/eslint-plugin --save-dev
```

## Configuration

Use the recommended [config](#recommended) to get reasonable defaults:

```json
"extends": [
"plugin:@react-three/recommended"
]
```

If you do not use a config you will need to specify individual rules and add extra configuration.

Add "@react-three" to the plugins section.

```json
"plugins": [
"@react-three"
]
```

Enable the rules that you would like to use.

```json
"rules": {
"@react-three/no-clone-in-frame-loop": "error"
}
```

## Rules

βœ… Enabled in the `recommended` [configuration](#recommended).<br>
πŸ”§ Automatically fixable by the `--fix` [CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).<br>
πŸ’‘ Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).

| Rule | Description | βœ… | πŸ”§ | πŸ’‘ |
| ---------------------- | --------------------------------------- | --- | --- | --- |
| no-clone-in-frame-loop | Disallow `.clone()` inside frame loops. | | | |

## Shareable configs

<!-- This part of the readme is not currently codegen'd. If you add more configs make sure to update this. -->

### Recommended

This plugin exports a `recommended` configuration that enforces rules appropriate for everyone using React Three Fiber.

```json
"extends": [
"plugin:@react-three/recommended"
]
```

### All

This plugin also exports an `all` configuration that includes every available rule.

```json
"extends": [
"plugin:@react-three/all"
]
```
50 changes: 50 additions & 0 deletions packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "@react-three/eslint-plugin",
"version": "0.0.0",
"description": "An ESLint plugin which provides lint rules for @react-three/fiber.",
"keywords": [
"react",
"renderer",
"fiber",
"three",
"threejs",
"eslint"
],
"author": "Michael Dougall (https://github.com/itsdouges)",
"license": "MIT",
"bugs": {
"url": "https://github.com/pmndrs/react-three-fiber/issues"
},
"homepage": "https://github.com/pmndrs/react-three-fiber/packages/eslint-plugin#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/pmndrs/react-three-fiber.git"
},
"collective": {
"type": "opencollective",
"url": "https://opencollective.com/react-three-fiber"
},
"main": "dist/react-three-eslint-plugin.cjs.js",
"module": "dist/react-three-eslint-plugin.esm.js",
"types": "dist/react-three-eslint-plugin.cjs.d.ts",
"sideEffects": false,
"preconstruct": {
"entrypoints": [
"index.ts"
]
},
"dependencies": {
"@babel/runtime": "^7.17.8",
"eslint": "^8.12.0"
},
"devDependencies": {
"@types/eslint": "^8.4.10",
"@types/lodash": "^4.14.191",
"ts-node": "^10.9.1",
"lodash": "^4.17.19",
"prettier": "^2.6.1"
},
"scripts": {
"codegen": "ts-node scripts/codegen.ts"
}
}
208 changes: 208 additions & 0 deletions packages/eslint-plugin/scripts/codegen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import type { Rule } from 'eslint'
import fs from 'fs/promises'
import { join, extname, relative } from 'path'
import { camelCase } from 'lodash'
import { format, resolveConfig } from 'prettier'

const jsHeader = (file: string) =>
`// THIS FILE WAS GENERATED DO NOT MODIFY BY HAND
// @command yarn codegen:eslint
` + file

const mdHeader = (file: string) =>
`<!--- THIS FILE WAS GENERATED DO NOT MODIFY BY HAND -->
<!--- @command yarn codegen:eslint -->
` + file

interface FoundRule {
module: Rule.RuleModule
moduleName: string
}

interface GeneratedConfig {
name: string
path: string
}

const ignore = ['index.ts']
const srcDir = join(__dirname, '../src')
const rulesDir = join(srcDir, 'rules')
const configsDir = join(srcDir, 'configs')
const generatedConfigs: GeneratedConfig[] = []

async function generateConfig(name: string, rules: FoundRule[]) {
const code = `
export default {
plugins: ['@react-three'],
rules: {
${rules.map((rule) => `'@react-three/${rule.moduleName}': 'error'`).join(',')}
},
}
`

const filepath = join(configsDir, `${name}.ts`)
await writeFile(filepath, code)

generatedConfigs.push({ name: camelCase(name), path: './' + relative(srcDir, join(configsDir, name)) })
}

async function writeFile(filepath: string, code: string) {
const config = await resolveConfig(filepath)
await fs.writeFile(
filepath,
format(extname(filepath) === '.md' ? mdHeader(code) : jsHeader(code), { ...config, filepath }),
)
}

async function generateRuleIndex(rules: FoundRule[]) {
const code = `
${rules.map((rule) => `import ${camelCase(rule.moduleName)} from './${rule.moduleName}'`).join('\n')}
export default {
${rules.map((rule) => `'${rule.moduleName}': ${camelCase(rule.moduleName)}`).join(',')}
}
`

const filepath = join(rulesDir, 'index.ts')
await writeFile(filepath, code)
}

async function generatePluginIndex() {
const code = `
${generatedConfigs.map((config) => `import ${config.name} from '${config.path}'`).join('\n')}
export { default as rules } from './rules/index'
export const configs = {
${generatedConfigs.map((config) => `${config.name}`).join(',')}
}
`

const filepath = join(srcDir, 'index.ts')
await writeFile(filepath, code)
}

const conditional = (cond: string, content?: boolean | string) => (content ? cond : '')
const link = (content: string, url?: string) => (url ? `<a href="${url}">${content}</a>` : content)

async function generateReadme(rules: FoundRule[]) {
const code = `
# @react-three/eslint-plugin
[![Version](https://img.shields.io/npm/v/@react-three/eslint-plugin?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/@react-three/eslint-plugin)
[![Twitter](https://img.shields.io/twitter/follow/pmndrs?label=%40pmndrs&style=flat&colorA=000000&colorB=000000&logo=twitter&logoColor=000000)](https://twitter.com/pmndrs)
[![Discord](https://img.shields.io/discord/740090768164651008?style=flat&colorA=000000&colorB=000000&label=discord&logo=discord&logoColor=000000)](https://discord.gg/ZZjjNvJ)
[![Open Collective](https://img.shields.io/opencollective/all/react-three-fiber?style=flat&colorA=000000&colorB=000000)](https://opencollective.com/react-three-fiber)
[![ETH](https://img.shields.io/badge/ETH-f5f5f5?style=flat&colorA=000000&colorB=000000)](https://blockchain.com/eth/address/0x6E3f79Ea1d0dcedeb33D3fC6c34d2B1f156F2682)
[![BTC](https://img.shields.io/badge/BTC-f5f5f5?style=flat&colorA=000000&colorB=000000)](https://blockchain.com/btc/address/36fuguTPxGCNnYZSRdgdh6Ea94brCAjMbH)
An ESLint plugin which provides lint rules for [@react-three/fiber](https://github.com/pmndrs/react-three-fiber).
## Installation
\`\`\`bash
npm install @react-three/eslint-plugin --save-dev
\`\`\`
## Configuration
Use the recommended [config](#recommended) to get reasonable defaults:
\`\`\`json
"extends": [
"plugin:@react-three/recommended"
]
\`\`\`
If you do not use a config you will need to specify individual rules and add extra configuration.
Add "@react-three" to the plugins section.
\`\`\`json
"plugins": [
"@react-three"
]
\`\`\`
Enable the rules that you would like to use.
\`\`\`json
"rules": {
"@react-three/no-clone-in-frame-loop": "error"
}
\`\`\`
## Rules
βœ… Enabled in the \`recommended\` [configuration](#recommended).<br>
πŸ”§ Automatically fixable by the \`--fix\` [CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).<br>
πŸ’‘ Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
| Rule | Description | βœ… | πŸ”§ | πŸ’‘ |
| ---- | -- | -- | -- | -- |
${rules
.map(
(rule) =>
`| ${link(rule.moduleName, rule.module.meta?.docs?.url)} | ${rule.module.meta?.docs?.description} | ${conditional(
'βœ…',
rule.module.meta?.docs?.recommended,
)} | ${conditional('πŸ”§', rule.module.meta?.fixable)} | ${conditional('πŸ’‘', rule.module.meta?.hasSuggestions)} |`,
)
.join('\n')}
## Shareable configs
<!-- This part of the readme is not currently codegen'd. If you add more configs make sure to update this. -->
### Recommended
This plugin exports a \`recommended\` configuration that enforces rules appropriate for everyone using React Three Fiber.
\`\`\`json
"extends": [
"plugin:@react-three/recommended"
]
\`\`\`
### All
This plugin also exports an \`all\` configuration that includes every available rule.
\`\`\`json
"extends": [
"plugin:@react-three/all"
]
\`\`\`
`

const filepath = join(srcDir, '../', 'README.md')
await writeFile(filepath, code)
}

async function generate() {
const rulePaths = await fs.readdir(rulesDir)
const recommended: FoundRule[] = []
const rules: FoundRule[] = []

for (const moduleName of rulePaths) {
if (ignore.includes(moduleName)) {
continue
}

const rule: Rule.RuleModule = (await import(join(rulesDir, moduleName))).default
const foundRule = { module: rule, moduleName: moduleName.replace(extname(moduleName), '') }
rules.push(foundRule)

if (rule.meta?.docs?.recommended) {
recommended.push(foundRule)
}
}

await generateRuleIndex(rules)
await generateConfig('all', rules)
await generateConfig('recommended', recommended)
await generatePluginIndex()
await generateReadme(rules)
}

generate()
Loading

0 comments on commit 1e0ca98

Please sign in to comment.