Skip to content

Commit

Permalink
feat: experimental support for Biome linter
Browse files Browse the repository at this point in the history
  • Loading branch information
kirillgroshkov committed Aug 10, 2024
1 parent 1ecd1c9 commit a0ef5a1
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 17 deletions.
4 changes: 4 additions & 0 deletions biome.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
"extends": ["./cfg/biome.jsonc"]
}
77 changes: 77 additions & 0 deletions cfg/biome.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
"files": {
"ignore": ["**/__exclude", "*.compact.json"]
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 100,
"attributePosition": "auto"
},
"organizeImports": { "enabled": true },
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"performance": {
"noDelete": "off" // todo
},
"correctness": {
// noUnusedImports + noUnusedVariables can replace eslint-plugin-unused-vars!
"noUnusedImports": "error",
"noUnusedVariables": "error",
"useArrayLiterals": "error"
},
"style": {
"useShorthandFunctionType": "error",
"useShorthandAssign": "error",
"useForOf": "error",
"useConsistentArrayType": "error",
"useShorthandArrayType": "error",
"noDefaultExport": "error",
"noCommaOperator": "error",
"noArguments": "error",
"noNonNullAssertion": "off",
"useImportType": "off",
"noParameterAssign": "off",
"useTemplate": "off",
"useNumberNamespace": "off",
"noUnusedTemplateLiteral": "off"
},
"suspicious": {
"noExplicitAny": "off",
"noAssignInExpressions": "off",
"noAsyncPromiseExecutor": "off",
"noPrototypeBuiltins": "off",
"noGlobalIsNan": "off", // todo,
"noThenProperty": "off",
"noImportAssign": "off",
"noEmptyInterface": "off"
},
"complexity": {
"noForEach": "off",
"noUselessThisAlias": "off",
"useLiteralKeys": "off",
"noBannedTypes": "off"
}
}
},
"javascript": {
"formatter": {
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"trailingCommas": "all",
"semicolons": "asNeeded",
"arrowParentheses": "asNeeded",
"bracketSpacing": true,
"bracketSameLine": false,
"quoteStyle": "single",
"attributePosition": "auto"
}
},
"overrides": [{ "include": ["tsconfig.json", "tsconfig.*.json"] }]
}
4 changes: 4 additions & 0 deletions cfg/init/biome.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
"extends": ["node_modules/@naturalcycles/dev-lib/cfg/biome.jsonc"]
}
28 changes: 20 additions & 8 deletions cfg/lint-staged.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ const stylelintExists =
fs.existsSync('node_modules/stylelint-config-standard-scss')
const stylelintCmd = stylelintExists ? `stylelint --fix --config ${stylelintConfigPath}` : undefined

const biomeInstalled = fs.existsSync('node_modules/@biomejs/biome')
const biomeConfigPath = biomeInstalled && ['biome.jsonc'].find(p => fs.existsSync(p))
const biomeCmd = biomeConfigPath && `biome lint --write --unsafe --`

if (!eslintConfigPathRoot) {
console.log('eslint is skipped, because ./eslint.config.js is not present')
}
Expand All @@ -47,11 +51,15 @@ if (!stylelintCmd) {
}

const linters = {
// *.{ts,tsx,vue} files: eslint, prettier
// *.{ts,tsx,vue} files: biome, eslint, prettier
'./src/**/*.{ts,tsx,vue}': match => {
const filesList = getFilesList(match)
if (!filesList) return []
return [eslintConfigPathRoot && `${eslintCmd} --config ${eslintConfigPathRoot}`, prettierCmd]
return [
biomeCmd,
eslintConfigPathRoot && `${eslintCmd} --config ${eslintConfigPathRoot}`,
prettierCmd,
]
.filter(Boolean)
.map(s => `${s} ${filesList}`)
},
Expand All @@ -74,20 +82,21 @@ const linters = {
// prettierCmd].map(s => `${s} ${filesList}`)
// },

// Files for Stylelint + Prettier
// Files for Biome + Stylelint + Prettier
[`./{${prettierDirs}}/**/*.{${stylelintExtensions}}`]: match => {
const filesList = getFilesList(match)
if (!filesList) return []
return [stylelintCmd, prettierCmd].filter(Boolean).map(s => `${s} ${filesList}`)
return [biomeCmd, stylelintCmd, prettierCmd].filter(Boolean).map(s => `${s} ${filesList}`)
},

// Files in root dir
// Files in root dir: prettier
[`./*.{${prettierExtensionsAll}}`]: match => {
const filesList = getFilesList(match)
if (!filesList || !prettierCmd) return []
return [prettierCmd].map(s => `${s} ${filesList}`)
},

// ktlint
'**/*.{kt,kts}': match => {
const filesList = getFilesList(match)
if (!filesList) return []
Expand Down Expand Up @@ -122,11 +131,12 @@ if (fs.existsSync(`./scripts`)) {
fs.existsSync(p),
)
Object.assign(linters, {
// eslint, Prettier
// biome, eslint, Prettier
'./scripts/**/*.{ts,tsx}': match => {
const filesList = getFilesList(match)
if (!filesList) return []
return [
biomeCmd,
eslintConfigPathScripts &&
`${eslintCmd} --config ${eslintConfigPathScripts} --parser-options=project:./scripts/tsconfig.json`,
prettierCmd,
Expand All @@ -144,11 +154,12 @@ if (fs.existsSync(`./e2e`)) {
)

Object.assign(linters, {
// eslint, Prettier
// biome, eslint, Prettier
'./e2e/**/*.{ts,tsx}': match => {
const filesList = getFilesList(match)
if (!filesList) return []
return [
biomeCmd,
eslintConfigPathE2e &&
`${eslintCmd} --config ${eslintConfigPathE2e} --parser-options=project:./e2e/tsconfig.json`,
prettierCmd,
Expand All @@ -166,11 +177,12 @@ if (fs.existsSync(`./playwright`)) {
)

Object.assign(linters, {
// eslint, Prettier
// biome, eslint, Prettier
'./playwright/**/*.{ts,tsx}': match => {
const filesList = getFilesList(match)
if (!filesList) return []
return [
biomeCmd,
eslintConfigPathE2e &&
`${eslintCmd} --config ${eslintConfigPathE2e} --parser-options=project:./playwright/tsconfig.json`,
prettierCmd,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"yargs": "^17.0.0"
},
"devDependencies": {
"@biomejs/biome": "^1.8.3",
"jest": "^29.0.0",
"stylelint": "^16.0.2",
"stylelint-config-standard-scss": "^13.0.0"
Expand Down
23 changes: 21 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@
[![Known Vulnerabilities](https://snyk.io/package/npm/snyk/badge.svg)](https://snyk.io/package/npm/@naturalcycles/dev-lib)
[![Actions](https://github.com/NaturalCycles/dev-lib/workflows/default/badge.svg)](https://github.com/NaturalCycles/dev-lib/actions)

## Tools that dev-lib enables

- Prettier
- ESLint
- Biome
- Stylelint
- Jest
- ktlint
- actionlint
- lint-staged

## How to use

Install it:
Expand All @@ -20,7 +31,7 @@ Install it:
This unlocks all commands listed below, e.g:

yarn dev-lib test
yarn dev-lib lint-all
yarn dev-lib lint

`yarn dev-lib` runs "interactive mode" that lets you explore available commands.

Expand Down Expand Up @@ -143,12 +154,13 @@ If you need to execute shards **in parallel**, you can follow e.g

#### Lint commands

- `lint`: runs ESLint, Stylelint, Prettier, actionlint, ktlint in the right order.
- `lint`: runs Biome, ESLint, Stylelint, Prettier, actionlint, ktlint in the right order.

- `--commitOnChanges` will commit lint-modified changes and push them
- `--failOnChanges` will exit with status 1 in the end (will fail the command)

- `eslint`: runs `eslint` on needed paths
- `biome`: runs `biome` on needed paths
- `stylelint`: runs `stylelint` on needed paths
- `prettier`: runs just Prettier on needed paths

Expand All @@ -163,6 +175,12 @@ For Stylelint to be run, you need to manually install it in the target project:

`yarn add -D stylelint stylelint-config-standard-scss`

For Biome to run you need to install it like this:

`yarn add -D @biomejs/biome`

and add `biome.jsonc` config to the root.

##### ktlint

`ktlint` will be used by lint-staged for all `**/*.{kt,kts}` files.
Expand Down Expand Up @@ -198,6 +216,7 @@ These files are meant to be extended in target project, so act as _recommended d
- `lint-staged.config.js`
- `prettier.config.js`
- `eslint.config.js`
- `biome.jsonc`
- `jest.config.js`

## eslint
Expand Down
12 changes: 12 additions & 0 deletions src/bin/dev-lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
eslintAll,
lintAllCommand,
lintStagedCommand,
runBiome,
runCommitlintCommand,
runPrettier,
stylelintAll,
Expand Down Expand Up @@ -81,6 +82,17 @@ const commands: (Command | Separator)[] = [
desc: 'Run eslint on all files with "auto-fix" disabled. Useful for debugging.',
interactiveOnly: true,
},
{
name: 'biome',
fn: () => runBiome(true),
desc: 'Run biome linter on all files.',
},
{
name: 'biome --no-fix',
fn: () => runBiome(true, false),
desc: 'Run biome linter on all files with "auto-fix" disabled. Useful for debugging.',
interactiveOnly: true,
},
{ name: 'prettier', fn: runPrettier, desc: 'Run prettier on all files.' },
{ name: 'stylelint', fn: stylelintAll, desc: 'Run stylelint on all files.' },
{ name: 'commitlint', fn: runCommitlintCommand, desc: 'Run commitlint.', cliOnly: true },
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export {}
export type {}
34 changes: 28 additions & 6 deletions src/lint.util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import cp, { type ExecSyncOptions } from 'node:child_process'
import fs from 'node:fs'
import { _since, _truncate } from '@naturalcycles/js-lib'
import { _isTruthy, _since, _truncate } from '@naturalcycles/js-lib'
import {
boldGrey,
commitMessageToTitleMessage,
Expand Down Expand Up @@ -49,6 +49,8 @@ export async function lintAllCommand(): Promise<void> {

runActionLint()

runBiome()

// From this point we start the "slow" linters, with ESLint leading the way

// We run eslint BEFORE Prettier, because eslint can delete e.g unused imports.
Expand Down Expand Up @@ -254,7 +256,8 @@ export async function lintStagedCommand(): Promise<void> {
// const lintStaged = require('lint-staged')
// lint-staged is ESM since 12.0
// const lintStaged = await import('lint-staged')
// eslint-disable-next-line no-eval
/* eslint-disable no-eval */
// biome-ignore lint/security/noGlobalEval: ok
const { default: lintStaged } = await eval(`import('lint-staged')`)
const success = await lintStaged({
configPath: config,
Expand Down Expand Up @@ -284,10 +287,9 @@ export function runCommitlintCommand(): void {
}

async function runKTLint(): Promise<void> {
if (fs.existsSync(`node_modules/@naturalcycles/ktlint`)) {
const ktlintLib = require('@naturalcycles/ktlint')
await ktlintLib.ktlintAll()
}
if (!fs.existsSync(`node_modules/@naturalcycles/ktlint`)) return
const ktlintLib = require('@naturalcycles/ktlint')
await ktlintLib.ktlintAll()
}

function runActionLint(): void {
Expand All @@ -305,6 +307,26 @@ function runActionLint(): void {
}
}

export function runBiome(verbose = false, fix = true): void {
if (!fs.existsSync(`node_modules/@biomejs/biome`)) {
if (verbose) console.log(`biome is not installed (checked in node_modules/@biomejs), skipping`)
return
}

const configPath = `biome.jsonc`
if (!fs.existsSync(configPath)) {
if (verbose) console.log(`biome is installed, but biome.jsonc config file is missing`)
return
}

const dirs = [`src`, `scripts`, `e2e`, `playwright`].filter(d => fs.existsSync(d))

execVoidCommandSync(
`biome`,
[`lint`, fix && '--write', fix && '--unsafe', ...dirs].filter(_isTruthy),
)
}

function canRunBinary(name: string): boolean {
try {
execSync(`which ${name}`)
Expand Down
Loading

0 comments on commit a0ef5a1

Please sign in to comment.