Skip to content
曾亮懿 ZengLiangYi Blog

Code tooling: the actual roles of Biome, ESLint, and formatting

Read ChatCrystal’s scripts and Flat Config, verify ESLint diagnostics offline, and distinguish syntax rules, type checking, formatting, and CI gates.

曾亮懿

曾亮懿

4 min read
Table of contents Show Hide

A tool listed in dependencies does not establish that every check is enabled. This article follows ChatCrystal’s actual commands, Biome switches, and client Flat Config, then verifies target diagnostics offline. The goal is a demonstrable scope, rather than adding tools.

Read scripts, then tool switches

The current root package.json runs Biome first for pnpm lint, then client ESLint if it succeeds. These are current script excerpts:

{
  "lint": "pnpm exec biome check . --diagnostic-level=error && pnpm --filter ./client lint",
  "lint:fix": "pnpm exec biome check --fix . && pnpm --filter ./client lint -- --fix"
}

biome.json enables the linter, while formatter and assist are both false. Current biome check –fix therefore does not establish repository-wide formatting. VCS ignores and ignoreUnknown also affect coverage; a dot in the command does not mean every file type receives identical checks.

Root dependencies do not integrate Prettier, husky, or lint-staged, and there is no .husky commit hook. Editor formatting and local commit checks remain possible additions. Teaching installation instructions from the original draft cannot be presented as existing project features.

Flat Config controls matching, parsing, and rules separately

client/eslint.config.js uses ESLint Flat Config. It globally ignores dist and combines these configurations for **/*.{ts,tsx}:

Configuration Current role
@eslint/js recommended Common JavaScript problem rules
typescript-eslint recommended TypeScript syntax and recommended rules
react-hooks flat recommended React Hooks rules
react-refresh vite Export conventions for Vite refresh

languageOptions uses ecmaVersion:latest and browser globals, rather than the original draft’s fixed 2020. React’s set-state-in-effect rule is disabled; that override does not disable every Hooks rule. Global ignores differ from matching only particular file types; see the official configuration guide.

The typescript-eslint recommended configuration here does not establish type-aware linting. Current settings do not wire type information through projectService or equivalent options. ESLint cannot be described as performing all compiler type analysis or enforcing enum naming by default. Rules needing type information have separate configuration requirements.

Verify rules through specific diagnostics

Install ESLint 10.10.0, matching the current client, in a new temporary directory. Save lesson.mjs and run with Node 24+. This independent JavaScript teaching configuration loads no ChatCrystal configuration and includes no React or TypeScript rules.

npm init -y
npm install --ignore-scripts --no-audit --no-fund eslint@10.10.0
import assert from 'node:assert/strict';
import { ESLint } from 'eslint';

const lint = new ESLint({
  overrideConfigFile: true,
  overrideConfig: [
    {
      files: ['**/*.js'],
      languageOptions: { globals: { console: 'readonly' } },
      rules: { 'no-unused-vars': 'error', 'no-undef': 'error' },
    },
  ],
});
const [bad] = await lint.lintText('const unused = 1;', { filePath: 'fixture.js' });
assert.equal(bad.errorCount, 1);
assert.equal(bad.messages[0].ruleId, 'no-unused-vars');
const [good] = await lint.lintText("console.log('ready');", { filePath: 'fixture.js' });
assert.equal(good.errorCount, 0);
const [missing] = await lint.lintText('missing();', { filePath: 'fixture.js' });
assert.equal(missing.messages[0].ruleId, 'no-undef');
console.log('specific diagnostics and repaired input pass');

Change no-unused-vars from error to off and the first error-count assertion should fail; restore it and the program passes. Checking ruleId also avoids confusing a parse error with the intended rule. lintText returns diagnostics; the API call does not automatically set process status from the error count. Assertions determine success or failure in this lesson.

Integration checks should cover a matched file, an ignored file, and a file with a rule override. From client, pnpm exec eslint --print-config src/App.tsx shows the merged configuration. Array order, file matching, and override values establish more than the mere presence of plugins.

Formatting needs its own implementation choice

To add formatting, first choose Biome or Prettier and specify supported file types, commands, and editor settings. Enabling Biome formatter or adding Prettier are future options; this article changes neither configuration.

eslint-config-prettier disables a set of potentially conflicting formatting rules. It does not run Prettier or disable every ESLint quality rule. Existing ESLint rules need not conflict with formatting, so the presence of two tools alone does not establish incompatibility.

Save-time fixes and CI checks serve different purposes: the former changes fixable items, while the latter verifies committed content. Automatic fixes cannot guarantee removal of every error; inspect the diff and check again. Commit hooks can be bypassed. Whether CI blocks merging also depends on repository required checks, which cannot be inferred from a workflow file alone.

Check commands and failure exits

Current npm and Electron publishing jobs in release.yml run pnpm lint and pnpm test before building. That release workflow triggers from tags or manual dispatch. It does not establish identical gates on every ordinary pull request.

Accept a check only after verifying the target diagnostic, passing repaired input, intended ignores, and a failure that actually stops downstream work. Type and runtime checks have separate roles in TypeScript monorepo configuration; passing lint does not replace a testing strategy either.

Share:

Related Posts

Five AI conversation parsers: retained fields and loss boundaries

Build an acceptance matrix from Claude Code, Codex, Cursor, Trae, and Copilot parsers, checking tool flags, thinking, order, and snapshot coverage.

曾亮懿 曾亮懿
4 min read
#ChatCrystal #TypeScript #Node.js

AI SDK Provider Abstraction: Capabilities and Protocols

Test 7 ChatCrystal providers and AI SDK factories with offline mocks for Responses, Chat Completions, embeddings, and structured output rejection.

曾亮懿 曾亮懿
4 min read
#ChatCrystal #Node.js #TypeScript

SourceAdapter internals: identity, content hashes, and reimport

Trace ChatCrystal’s registry, importer, and remote payload to verify identity, canonical hashes, metadata shortcuts, and parser-version boundaries.

曾亮懿 曾亮懿
4 min read
#ChatCrystal #TypeScript #Node.js

From writing to code

Explore ChatCrystal, developer tools and daily learning notes on my GitHub. Questions about the implementation are welcome.