Skip to content

ESLint Rules ​

Mikro.js ships @mikrojs/eslint-plugin, a set of ESLint rules that enforce the error handling conventions and catch patterns that don't work well on microcontrollers. The plugin is enabled by default in new projects.

Setup ​

Add the recommended config to your eslint.config.ts:

ts
import mikrojs from '@mikrojs/eslint-plugin'

export default [...mikrojs.configs.recommended]

This enables all rules below with their default severities.

Rules ​

@mikrojs/no-unhandled-result ​

Severity: error

Flags Result return values that aren't handled. If a function returns Result<T, E> or Promise<Result<T, E>> and the call expression isn't assigned to a variable or used in an expression, the error is silently lost.

typescript
// Bad: error is silently ignored
sensor.read()

// Good: error is checked
const result = sensor.read()
if (!result.ok) return result

@mikrojs/no-throw ​

Severity: error

Flags throw statements. Expected errors should be returned as Result types. For truly unrecoverable situations, use panic() from mikro/sys.

typescript
// Bad
throw new Error('sensor failed')

// Good
return err(SensorError.ReadFailed('sensor failed'))

// Good (for unrecoverable situations)
panic('sensor hardware is missing')

@mikrojs/no-try-catch ​

Severity: error

Flags try/catch blocks. Since Mikro.js functions return Result types, try/catch shouldn't be needed. try/finally for cleanup is allowed.

typescript
// Bad
try {
  const value = sensor.read()
} catch (err) {
  console.error(err)
}

// Good
const result = sensor.read()
if (!result.ok) {
  console.error(result.error)
}

// Allowed (try/finally for cleanup)
try {
  doSomething()
} finally {
  cleanup()
}

Standard JavaScript functions still throw: JSON.parse() on invalid input, atob() on a malformed string, decodeURIComponent() on invalid sequences. These aren't Mikro.js APIs and have no Result equivalent, so catching them is the correct thing to do. Disable the rule at the call site and convert to a Result immediately:

typescript
function parseJson(text: string): Result<unknown, {name: 'InvalidJson'}> {
  // eslint-disable-next-line @mikrojs/no-try-catch -- JSON.parse has no Result form
  try {
    return ok(JSON.parse(text))
  } catch {
    return err({name: 'InvalidJson'})
  }
}

The same applies at a native module boundary, where a C function throws instead of returning a Result. See What about exceptions?.

@mikrojs/no-promise-reject ​

Severity: error

Flags Promise.reject() calls and reject() callback invocations. Use err() from mikro/result instead.

typescript
// Bad: caller has to try/catch to handle this
async function readSensor(): Promise<number> {
  const result = sensor.read()
  if (!result.ok) return Promise.reject(new Error('read failed'))
  return result.value
}

// Good: caller sees the error in the return type
async function readSensor(): Promise<Result<number, GpioError>> {
  const result = sensor.read()
  if (!result.ok) return result
  return ok(result.value)
}

@mikrojs/no-dot-catch ​

Severity: error

Flags .catch() on promises. Async errors should be returned as Result types, not caught with .catch().

typescript
// Bad
fetchData().catch((err) => console.error(err))

// Good
const result = await fetchData()
if (!result.ok) console.error(result.error)

@mikrojs/no-eval ​

Severity: error

Flags eval() calls and new Function() expressions. Runtime compilation is expensive on constrained devices and poses security risks.

@mikrojs/no-intl ​

Severity: error

Flags access to the Intl API. The Intl API is not available in QuickJS-NG. Use manual formatting or a lightweight library instead.

@mikrojs/no-temporal ​

Severity: error

Flags access to the Temporal API. The Temporal API is not available in QuickJS-NG. Use Date or a lightweight library instead.

@mikrojs/no-sparse-arrays ​

Severity: warn

Flags array literals with holes (for example [1,,3]) and delete on array elements. Use Array.prototype.splice() or filter instead.

Mikro.js is built with AI assistance, code and docs alike. Read the AI disclosure.