-
Notifications
You must be signed in to change notification settings - Fork 92
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add basic support for Biome (#316)
* feat: add basic support for Biome * add changeset * fix: tweak * chore: should be patch * fix: vitest * fix: Windows * update windows snapshots * normalize path returned by biome --------- Co-authored-by: fi3ework <fi3ework@gmail.com>
- Loading branch information
1 parent
92b63b2
commit 568b782
Showing
25 changed files
with
861 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'vite-plugin-checker': patch | ||
--- | ||
|
||
Added basic support for Biome |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
{ | ||
"vetur.experimental.templateInterpolationService": true, | ||
"typescript.tsdk": "node_modules/typescript/lib" | ||
"typescript.tsdk": "node_modules/typescript/lib", | ||
"vitest.disableWorkspaceWarning": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Biome | ||
|
||
## Installation | ||
|
||
1. Make sure [@biomejs/biome](https://www.npmjs.com/package/@biomejs/biome) is installed as peer dependency. | ||
|
||
2. Add `biome` field to plugin config. The exact command to be run can be further configured with `command` and `flags` parameters. See [the documentation](https://biomejs.dev/reference/cli/) for CLI reference. The default root of the command uses Vite's [root](https://vitejs.dev/config/#root). | ||
|
||
:::tip | ||
Do not add `--apply` to the flags since the plugin is only aiming at checking issues. | ||
::: | ||
|
||
```js | ||
// e.g. | ||
export default { | ||
plugins: [ | ||
checker({ | ||
biome: { | ||
command: 'check', | ||
}, | ||
}), | ||
], | ||
} | ||
``` | ||
|
||
## Configuration | ||
|
||
Advanced object configuration table of `options.biome` | ||
|
||
| field | Type | Default value | Description | | ||
| :----------- | --------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------- | | ||
| command | `'check' \| 'lint' \| 'format' \| 'ci'` | `'lint'` in dev, `'check'` in build | The command to execute biome with | | ||
| flags | `string` | `''` | CLI flags to pass to the command | | ||
| dev.logLevel | `('error' \| 'warning')[]` | `['error', 'warning']` | **(Only in dev mode)** Which level of Biome diagnostics should be emitted to terminal and overlay in dev mode | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { exec } from 'node:child_process' | ||
import path from 'node:path' | ||
import strip from 'strip-ansi' | ||
import { createFrame } from '../../codeFrame.js' | ||
import type { NormalizedDiagnostic } from '../../logger.js' | ||
import { DiagnosticLevel } from '../../types.js' | ||
import type { BiomeOutput } from './types.js' | ||
|
||
export const severityMap = { | ||
error: DiagnosticLevel.Error, | ||
warning: DiagnosticLevel.Warning, | ||
info: DiagnosticLevel.Suggestion, | ||
} as const | ||
|
||
export function getBiomeCommand(command: string, flags: string, files: string) { | ||
const defaultFlags = '--reporter json' | ||
return ['biome', command || 'lint', flags, defaultFlags, files].filter(Boolean).join(' ') | ||
} | ||
|
||
export function runBiome(command: string, cwd: string) { | ||
return new Promise<NormalizedDiagnostic[]>((resolve, reject) => { | ||
exec( | ||
command, | ||
{ | ||
cwd, | ||
}, | ||
(error, stdout, stderr) => { | ||
resolve([...parseBiomeOutput(stdout)]) | ||
} | ||
) | ||
}) | ||
} | ||
|
||
function parseBiomeOutput(output: string) { | ||
let parsed: BiomeOutput | ||
try { | ||
parsed = JSON.parse(output) | ||
} catch (e) { | ||
return [] | ||
} | ||
|
||
const diagnostics: NormalizedDiagnostic[] = parsed.diagnostics.map((d) => { | ||
let file = d.location.path?.file | ||
if (file) file = path.normalize(file) | ||
|
||
const loc = { | ||
file: file || '', | ||
start: getLineAndColumn(d.location.sourceCode, d.location.span?.[0]), | ||
end: getLineAndColumn(d.location.sourceCode, d.location.span?.[1]), | ||
} | ||
|
||
const codeFrame = createFrame(d.location.sourceCode || '', loc) | ||
|
||
return { | ||
message: `[${d.category}] ${d.description}`, | ||
conclusion: '', | ||
level: severityMap[d.severity as keyof typeof severityMap] ?? DiagnosticLevel.Error, | ||
checker: 'Biome', | ||
id: file, | ||
codeFrame, | ||
stripedCodeFrame: codeFrame && strip(codeFrame), | ||
loc, | ||
} | ||
}) | ||
|
||
return diagnostics | ||
} | ||
|
||
function getLineAndColumn(text?: string, offset?: number) { | ||
if (!text || !offset) return { line: 0, column: 0 } | ||
|
||
let line = 1 | ||
let column = 1 | ||
|
||
for (let i = 0; i < offset; i++) { | ||
if (text[i] === '\n') { | ||
line++ | ||
column = 1 | ||
} else { | ||
column++ | ||
} | ||
} | ||
|
||
return { line, column } | ||
} |
147 changes: 147 additions & 0 deletions
147
packages/vite-plugin-checker/src/checkers/biome/main.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import path from 'node:path' | ||
import { fileURLToPath } from 'node:url' | ||
import { parentPort } from 'node:worker_threads' | ||
import chokidar from 'chokidar' | ||
import { Checker } from '../../Checker.js' | ||
import { FileDiagnosticManager } from '../../FileDiagnosticManager.js' | ||
import { | ||
composeCheckerSummary, | ||
consoleLog, | ||
diagnosticToRuntimeError, | ||
diagnosticToTerminalLog, | ||
filterLogLevel, | ||
toClientPayload, | ||
} from '../../logger.js' | ||
import { ACTION_TYPES, type CreateDiagnostic, DiagnosticLevel } from '../../types.js' | ||
import { getBiomeCommand, runBiome, severityMap } from './cli.js' | ||
|
||
const __filename = fileURLToPath(import.meta.url) | ||
|
||
const manager = new FileDiagnosticManager() | ||
let createServeAndBuild: any | ||
|
||
const createDiagnostic: CreateDiagnostic<'biome'> = (pluginConfig) => { | ||
let overlay = true | ||
let terminal = true | ||
|
||
let command = '' | ||
let flags = '' | ||
|
||
if (typeof pluginConfig.biome === 'object') { | ||
command = pluginConfig.biome.command || '' | ||
flags = pluginConfig.biome.flags || '' | ||
} | ||
|
||
return { | ||
config: async ({ enableOverlay, enableTerminal }) => { | ||
overlay = enableOverlay | ||
terminal = enableTerminal | ||
}, | ||
async configureServer({ root }) { | ||
if (!pluginConfig.biome) return | ||
|
||
const logLevel = (() => { | ||
if (typeof pluginConfig.biome !== 'object') return undefined | ||
const userLogLevel = pluginConfig.biome.dev?.logLevel | ||
if (!userLogLevel) return undefined | ||
|
||
return userLogLevel.map((l) => severityMap[l]) | ||
})() | ||
|
||
const dispatchDiagnostics = () => { | ||
const diagnostics = filterLogLevel(manager.getDiagnostics(), logLevel) | ||
|
||
if (terminal) { | ||
for (const d of diagnostics) { | ||
consoleLog(diagnosticToTerminalLog(d, 'Biome')) | ||
} | ||
|
||
const errorCount = diagnostics.filter((d) => d.level === DiagnosticLevel.Error).length | ||
const warningCount = diagnostics.filter((d) => d.level === DiagnosticLevel.Warning).length | ||
consoleLog(composeCheckerSummary('Biome', errorCount, warningCount)) | ||
} | ||
|
||
if (overlay) { | ||
parentPort?.postMessage({ | ||
type: ACTION_TYPES.overlayError, | ||
payload: toClientPayload( | ||
'biome', | ||
diagnostics.map((d) => diagnosticToRuntimeError(d)) | ||
), | ||
}) | ||
} | ||
} | ||
|
||
const handleFileChange = async (filePath: string, type: 'change' | 'unlink') => { | ||
const absPath = path.resolve(root, filePath) | ||
if (type === 'unlink') { | ||
manager.updateByFileId(absPath, []) | ||
} else if (type === 'change') { | ||
const isConfigFile = path.basename(absPath) === 'biome.json' | ||
|
||
if (isConfigFile) { | ||
const runCommand = getBiomeCommand(command, flags, root) | ||
const diagnostics = await runBiome(runCommand, root) | ||
manager.initWith(diagnostics) | ||
} else { | ||
const runCommand = getBiomeCommand(command, flags, absPath) | ||
const diagnosticsOfChangedFile = await runBiome(runCommand, root) | ||
manager.updateByFileId(absPath, diagnosticsOfChangedFile) | ||
} | ||
} | ||
|
||
dispatchDiagnostics() | ||
} | ||
|
||
// initial check | ||
const runCommand = getBiomeCommand(command, flags, root) | ||
const diagnostics = await runBiome(runCommand, root) | ||
|
||
manager.initWith(diagnostics) | ||
dispatchDiagnostics() | ||
|
||
// watch lint | ||
const watcher = chokidar.watch([], { | ||
cwd: root, | ||
ignored: (path: string) => path.includes('node_modules'), | ||
}) | ||
watcher.on('change', async (filePath) => { | ||
handleFileChange(filePath, 'change') | ||
}) | ||
watcher.on('unlink', async (filePath) => { | ||
handleFileChange(filePath, 'unlink') | ||
}) | ||
watcher.add('.') | ||
}, | ||
} | ||
} | ||
|
||
export class BiomeChecker extends Checker<'biome'> { | ||
public constructor() { | ||
super({ | ||
name: 'biome', | ||
absFilePath: __filename, | ||
build: { | ||
buildBin: (pluginConfig) => { | ||
if (typeof pluginConfig.biome === 'object') { | ||
const { command, flags } = pluginConfig.biome | ||
return ['biome', [command || 'lint', flags || ''] as const] | ||
} | ||
return ['biome', ['lint']] | ||
}, | ||
}, | ||
createDiagnostic, | ||
}) | ||
} | ||
|
||
public init() { | ||
const _createServeAndBuild = super.initMainThread() | ||
createServeAndBuild = _createServeAndBuild | ||
super.initWorkerThread() | ||
} | ||
} | ||
|
||
export { createServeAndBuild } | ||
const biomeChecker = new BiomeChecker() | ||
biomeChecker.prepare() | ||
biomeChecker.init() |
Oops, something went wrong.