Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds ESLint package @react-three/eslint-plugin #2698

Merged
merged 8 commits into from
Jan 12, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
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
Copy link
Contributor Author

@itsdouges itsdouges Jan 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Figured we just keep bumping minors until we release a major version. This will release the package under v0.1.0.

---

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: {
Copy link
Contributor Author

@itsdouges itsdouges Jan 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this since it was commented out.

// global: {
// statements: 80,
// branches: 68,
// functions: 80,
// lines: 80,
// },
// },
coverageDirectory: './coverage/',
collectCoverage: true,
collectCoverage: false,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turned this to false since it was on for all unit tests, annoying! It's still enabled for yarn test.

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",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does yarn@1 have workspaces-run inbuilt? Need to search over docs.

"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'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module generates most content inside the eslint plugin, including: rule index, configs, and readme. It is a very simple implementation.

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'`)}
},
}
`

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}'`)}

export default {
${rules.map((rule) => `'${rule.moduleName}': ${camelCase(rule.moduleName)}`)}
}
`

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('\n')}
}
`

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() {
Copy link
Contributor Author

@itsdouges itsdouges Jan 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surprised this doesn't exist as a standalone npm pkg! Let me know if it does and my searchfu needs work.

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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we ever want to add another config it's just a matter of calling generate config again. Perhaps we could introduce a native config if that made sense?

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

generate()
Loading