Skip to content

Instantly share code, notes, and snippets.

@rajinder-yadav
Last active December 27, 2025 12:03
Show Gist options
  • Select an option

  • Save rajinder-yadav/ba8a64afdeb3cee33063ee344e68aae4 to your computer and use it in GitHub Desktop.

Select an option

Save rajinder-yadav/ba8a64afdeb3cee33063ee344e68aae4 to your computer and use it in GitHub Desktop.
Linting and formatting with Biome and Typechecking with TSC

Linting and formatting with Biome and Typechecking with TSC

The Biome config file is setup only to focus on TypeScript files and ignored ".js" file. If you want to include JS files, then continue reading on what to do. (Hint: remove ".js" file filtering).

Write up assisted with AI, double check information being provided.

Installing recommended Node.js dependencies

pnpm i -D -E @biomejs/biome @types/node
pnpm i typescript

Basic command like usage:

Format, lint, and organize imports of all files

pnpm exec biome check --write

Format and Lint and apply safe fixes

pnpm exec biome format --write

pnpm exec biome lint --write

NPM scripts

  "scripts": {
    "build": "tsc",
    "check": "tsc --noEmit",
    "watch": "tsc -w",
    "format:lint": "pnpm exec biome check --write",
    "format": "biome format --write",
    "lint":"biome lint --write",
    "tsc": "tsc"
  },

Biome configuration

Detailed explanation follows. NOTE: remove comment from ".json" file.

File: biome.json

{
  "$schema": "https://biomejs.dev/schemas/latest/schema.json",
  "vcs": {
    "enabled": true,
    "clientKind": "git",
    "useIgnoreFile": true
  },
  "files": {
    "ignore": [
      "node_modules/**",
      "dist/**",
      "build/**",
      ".next/**",
      "out/**",
      "coverage/**",
      "public/**",
      "*.d.ts".
      "*.js",
      "*.min.js",
      "*.min.css",
      "!.github",
      "!.vscode"
    ]
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 120,
    "lineEnding": "lf",
    "attributePosition": "auto"
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "double",
      "jsxQuoteStyle": "double",
      "trailingComma": "es5",
      "semicolons": "always",
      "arrowParentheses": "always",
      "bracketSpacing": true,
      "bracketSameLine": false,
      "quoteProperties": "asNeeded"
    },
    "linter": {
      "enabled": true
    },
    "organizeImports": {
      "enabled": true
    }
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      // 🔒 Enable stricter type-related rules
      "correctness": {
        "noUndeclaredVariables": "error",
        "noUnusedVariables": "error"
      },
      "style": {
        // Enforce type annotations where inference might be unsafe
        "noInferrableTypes": "off", // Keep explicit types even if redundant (optional)
        "useExplicitType": "error"  // 👈 KEY: Requires explicit type annotations in declarations
      },
      "suspicious": {
        "noExplicitAny": "error",   // 🔥 Ban `any` entirely (stronger than noImplicitAny!)
        "noMisusedPromises": "error"
      }
    }
  },
  "overrides": [
    {
      "include": ["*.js"],
      "linter": {
        "enabled": false
      }
    }
  ]
}

Let's break down your Biome configuration file section by section, explaining what each property does, why it matters, and what the possible values mean—based on the official Biome JSON schema you provided.


📄 Overall Structure

Your config is a JSON object that controls:

  • How Biome formats code
  • Which files to ignore
  • How it lints (static analysis)
  • How it interacts with Git
  • How it overrides behavior for specific file types

Now, let’s go line by line.


1. "$schema"

"$schema": "https://biomejs.dev/schemas/latest/schema.json"

✅ Purpose:

  • Tells editors (like VS Code) and tools where to find the JSON Schema for validation and autocomplete.
  • Using /latest/ ensures you always get the schema for the most recent stable Biome version.

🔍 Schema Reference:

  • Defined in root as:
    "type": ["string", "null"]
  • Optional but highly recommended for tooling support.

2. "vcs" — Version Control System Integration

"vcs": {
  "enabled": true,
  "clientKind": "git",
  "useIgnoreFile": true
}

🔧 Properties:

Property Value Meaning
enabled true Enables VCS integration (Git-aware features)
clientKind "git" Only supported VCS. Tells Biome to use Git.
useIgnoreFile true Biome respects .gitignore — ignores files Git ignores

💡 Why it matters:

  • Prevents linting/formatting of node_modules/, build artifacts, etc., even if not listed in files.ignore.
  • Makes config DRY — you don’t duplicate ignore patterns.

✅ Best practice: keep useIgnoreFile: true unless you have a special reason not to.


3. "files" — File System Behavior

"files": {
  "ignore": [
    "node_modules/**",
    "dist/**",
    "build/**",
    ".next/**",
    "out/**",
    "coverage/**",
    "public/**",
    "*.d.ts",
    "*.js",
    "*.min.js",
    "*.min.css",
    "!.github",
    "!.vscode"
  ]
}

🔧 Purpose:

  • Explicitly tells Biome which files/directories to skip entirely (for formatting, linting, and import sorting).

📂 Pattern Rules:

  • Uses Unix shell-style glob patterns
  • /** = match recursively (e.g., dist/** matches dist/a/b/c.js)
  • *.min.js = matches any minified JS file
  • ! = negation (un-ignore). So:
    • !.githubdo process .github/ (e.g., workflows)
    • !.vscodedo process .vscode/settings.json

⚠️ Important: These are absolute ignores — Biome won’t touch these files at all.

🆚 vs .gitignore:

  • files.ignore is Biome-specific
  • .gitignore is used only if "useIgnoreFile": true
  • Use files.ignore for things you always want ignored, even outside Git

4. "formatter" — Global Formatting Rules

"formatter": {
  "enabled": true,
  "indentStyle": "space",
  "indentWidth": 2,
  "lineWidth": 80,
  "lineEnding": "lf",
  "attributePosition": "auto"
}

These settings apply to all supported languages unless overridden.

Property Value Effect
enabled true Turns on formatting
indentStyle "space" Uses spaces (not tabs)
indentWidth 2 2 spaces per indent
lineWidth 80 Wraps lines at 80 characters
lineEnding "lf" Unix-style line endings (\n)
attributePosition "auto" In JSX/HTML, puts attributes on same line if they fit, otherwise multi-line

📝 These are generic. Language-specific overrides (like in javascript.formatter) take precedence.


5. "javascript" — JavaScript/TypeScript-Specific Settings

This section configures behavior only for .js, .ts, .jsx, .tsx files.

5.1 javascript.formatter

"formatter": {
  "quoteStyle": "double",
  "jsxQuoteStyle": "double",
  "trailingComma": "es5",
  "semicolons": "always",
  "arrowParentheses": "always",
  "bracketSpacing": true,
  "bracketSameLine": false,
  "quoteProperties": "asNeeded"
}
Property Value Meaning
quoteStyle "double" "hello" not 'hello'
jsxQuoteStyle "double" Same for JSX attributes: <div id="main">
trailingComma "es5" Trailing commas in arrays/objects, but not in function params (ES5-safe)
semicolons "always" Always adds ; (e.g., const x = 1;)
arrowParentheses "always" (x) => x instead of x => x
bracketSpacing true { foo: 1 } (space inside braces)
bracketSameLine false JSX closing > on new line:
<div
  id="x"
>
  content
</div>

| quoteProperties | "asNeeded" | Only quotes object keys if required: { valid: 1, "invalid-key": 2 } |

✅ These match common TypeScript style guides (e.g., Airbnb, Standard variants).

5.2 javascript.linter

"linter": {
  "enabled": true
}
  • Enables JavaScript/TypeScript-aware linting (type inference, React hooks, etc.)
  • Works with top-level "linter" rules

5.3 javascript.organizeImports

"organizeImports": {
  "enabled": true
}
  • Automatically sorts and removes unused imports
  • Equivalent to “Organize Imports” in VS Code

6. "linter" — Global Linting Configuration

"linter": {
  "enabled": true,
  "rules": {
    "recommended": true
  }
}
Property Value Meaning
enabled true Turns on linting
rules.recommended true Enables Biome’s recommended rule set, including:
- correctness (e.g., no unreachable code)
- suspicious (e.g., no ==, no console.log)
- style (e.g., prefer const)
- a11y, security, complexity, etc.

🛠️ These rules auto-fix when safe (e.g., unused imports, formatting issues).


7. "overrides" — Per-File-Type Customization

"overrides": [
  {
    "include": ["*.js"],
    "linter": {
      "enabled": false
    }
  }
]

🔧 Purpose:

  • Apply different settings to specific file patterns.

In This Case:

  • For all .js files:
    • Disable linting ("linter": { "enabled": false })
    • But keep formatting and import sorting (since those aren’t overridden)

✅ Common use case: You’re migrating a JS codebase to TS, and don’t want lint errors in legacy .js files.

⚙️ How Overrides Work:

  • include: list of glob patterns
  • Any setting you specify here overrides the global or language config only for matching files
  • You can override formatter, linter, organizeImports, etc.

❗ Note: If you wanted to fully ignore .js files, you’d put *.js in files.ignore instead.


Enabling script Typescript rule

Great! You'd like to enable noImplicitAny — a strict TypeScript rule that prevents the compiler (and linter) from inferring the any type when types aren't explicitly provided.

However, there's an important detail to understand first:


⚠️ Biome vs TypeScript Compiler

  • Biome does not use the TypeScript compiler (tsc).
  • Instead, Biome has its own type inference engine and linter rules.
  • Therefore, Biome doesn’t have a rule literally named noImplicitAny, but it does have equivalent or stricter rules that catch similar issues.

In Biome, the closest and most relevant rule is:

noImplicitAnyLet (part of the correctness group)
🔹 Actually, more broadly: noImplicitAny-like behavior is covered by:

  • noUndeclaredVariables
  • noUnsafeDeclarationMerging
  • And especially: noConfusingVoidType, noRedundantUndefined, etc.

But the key rule that aligns with noImplicitAny is:

🎯 noInferrableTypes is NOT it — that’s the opposite!

The real counterpart is enforced via type-aware lint rules that detect missing type annotations in contexts where any would be inferred.

However, as of Biome v1.9+, there isn’t a direct 1:1 noImplicitAny rule like in tsconfig.json.


✅ Recommended Approach in Biome

To approximate noImplicitAny behavior, you should:

  1. Enable stricter type-related linter rules
  2. Explicitly turn on rules that require type annotations in risky places

Here’s how to update your config:


🔍 Key Rules Explained

Rule Purpose Why It Helps Replace noImplicitAny
useExplicitType Requires explicit type annotations on variable/function declarations (e.g., let x: number = 5) Prevents silent any by making you write types — this is Biome’s closest equivalent
noExplicitAny Disallows writing any explicitly Stronger than noImplicitAny — if you ban any, you’re forced to use proper types
noUndeclaredVariables Catches variables used without being declared Catches JS-style bugs that TS would flag
noUnusedVariables Removes dead code Helps keep types clean

💡 Important: useExplicitType is not enabled by default, even in recommended. You must opt in.


🚫 But What About Implicit any in Function Params?

Example:

function greet(name) { // ❌ Biome won’t infer type → potential `any`
  return "Hello " + name;
}

With useExplicitType: "error", Biome will require:

function greet(name: string) {
  return "Hello " + name;
}

This eliminates implicit any at the source.


✅ Alternative: Use TypeScript Compiler Too

If you absolutely need noImplicitAny as defined by TypeScript, you should:

  • Keep using tsconfig.json with:
    {
      "compilerOptions": {
        "noImplicitAny": true
      }
    }
  • And run tsc --noEmit in CI to catch type errors
  • Use Biome for formatting + fast linting, and tsc for deep type checking

Many teams use both!


Final Recommendation

For most projects, enabling:

"useExplicitType": "error",
"noExplicitAny": "error"

…gives you better safety than noImplicitAny alone, because it:

  • Forces explicit types
  • Bans any completely

Let me know if you want to:

  • Only apply this to .ts files (not .tsx)
  • Allow any in test files
  • Auto-fix missing types (note: Biome cannot auto-infer and insert types — that’s beyond its scope)

✅ Summary of Your Config Behavior

Feature For .ts/.tsx For .js
Formatting ✅ Yes (with your rules) ✅ Yes (same rules)
Linting ✅ Yes (recommended rules) Disabled
Import Sorting ✅ Yes ✅ Yes
Respects .gitignore ✅ Yes ✅ Yes
Ignores dist/, node_modules/, etc. ✅ Yes ✅ Yes

🔧 Suggestions for Improvement

  1. Consider linting .js files later — maybe relax rules instead of disabling:

    "overrides": [{
      "include": ["*.js"],
      "linter": {
        "rules": {
          "suspicious": { "noConsole": "warn" },
          "correctness": { "noUnusedVariables": "off" }
        }
      }
    }]
  2. Add explicit typescript support?
    → Not needed! Biome treats .ts/.tsx as part of javascript config.


Type checking using TypeScript

running npx tsc --noEmit will catch TypeScript type-checking errors, but only those enforced by the TypeScript compiler (tsc) based on your tsconfig.json.


✅ What tsc --noEmit does:

  • Performs full type checking using your project’s tsconfig.json
  • Does NOT emit (output) any JavaScript files (thanks to --noEmit)
  • Exits with a non-zero code if there are type errors → useful in CI
  • Validates:
    • Type mismatches
    • Missing properties
    • Incorrect function signatures
    • noImplicitAny violations
    • strictNullChecks, strictFunctionTypes, etc.
    • Import/export resolution
    • Decorator and JSX configuration issues

✅ This is the standard way to type-check a TypeScript project without building it.


🆚 Biome vs tsc --noEmit

Feature tsc --noEmit Biome
Type checking ✅ Full, deep, semantic (uses TS compiler) No full type checking (as of Biome v1.9)
Linting Limited (mostly syntax/structure) ✅ Rich linting (noExplicitAny, useExplicitType, etc.)
Formatting ❌ No ✅ Yes
Speed Slower (full type inference) Very fast
Catches noImplicitAny ✅ Yes, if enabled in tsconfig.json ❌ Not directly — must use useExplicitType + noExplicitAny as workaround

🔑 Key point: Biome does not replace tsc --noEmit for type safety. They are complementary.


✅ Recommended Workflow

  1. Use Biome for:

    • Formatting
    • Fast linting (stylistic & common bugs)
    • Import sorting
    • Quick auto-fixes
  2. Use tsc --noEmit (or tsc --noEmit --project tsconfig.json) for:

    • Guaranteed type correctness
    • Enforcing noImplicitAny, strict, etc.

💡 Many teams run both in CI:

npx @biomejs/biome check .   # lint + format check
npx tsc --noEmit             # full type check

⚙️ Ensure Your tsconfig.json is Strict (Example)

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "strict": true,
    "noImplicitAny": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "noEmit": true
  }
}

Then just run:

npx tsc

(since noEmit is in config)


Summary

Yes, npx tsc --noEmit will catch TypeScript type errors — and it’s essential for full type safety.
🛠️ Biome enhances your workflow but does not replace the TypeScript compiler for type checking.

Let me know if you want a script that runs both Biome and tsc together!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment