guides
linting

Linting

Lint Bamboo CSS usage with ESLint, against the same config your build reads.

@bamboocss/eslint-plugin checks how your code uses Bamboo CSS, in the editor and in CI, against the config your build reads: a token path that does not exist, a value the build cannot resolve, a hardcoded color where a token belongs, a recipe the build cannot name.

Installation

pnpm add -D @bamboocss/eslint-plugin

Configuration

ESLint 9 and later read flat config, eslint.config.js. Add the recommended rules:

// eslint.config.js
import bamboo from '@bamboocss/eslint-plugin'
import tsParser from '@typescript-eslint/parser'
 
export default [{ files: ['**/*.{ts,tsx}'], languageOptions: { parser: tsParser } }, bamboo.configs['flat/recommended']]

bamboo.configs['flat/all'] turns on every rule instead, except prefer-shorthand-properties, the opposite of prefer-longhand-properties: with both on, each would undo the other's fixes. With defineConfig, the same is extends: ['bamboo/flat/recommended'] beside plugins: { bamboo }.

The configs name no parser: TypeScript files take the one your project configures — the line above, or typescript-eslint's configs — and .vue, .svelte and .astro files keep their own. In a file ESLint lints as another language — CSS through @eslint/css, JSON through @eslint/json — the rules stand aside.

To choose the rules yourself, register the plugin and set them:

export default [
  {
    plugins: { bamboo },
    rules: {
      'bamboo/no-invalid-token-paths': 'error',
      'bamboo/no-hardcoded-color': 'warn',
    },
  },
]

In eslint.config.ts, the plugin and its configs type-check as ESLint's own ESLint.Plugin and Linter.Config.

Legacy .eslintrc

For the config system ESLint 9 deprecated, recommended and all are .eslintrc configs. They name @typescript-eslint/parser, which has to be installed:

{
  "extends": ["plugin:bamboo/recommended"]
}

Settings

The rules read the bamboo.config.ts found from the file being linted. To read another, name the file — or the directory holding it — relative to ESLint's working directory. For a path relative to eslint.config.js instead, make it absolute:

import path from 'node:path'
 
export default [
  {
    settings: { '@bamboocss/configPath': path.join(import.meta.dirname, 'bamboo.config.ts') },
  },
]

Rules

✅ marks the rules in flat/recommended and recommended.

RuleDescription✅
file-not-included (opens in a new tab)Disallow Bamboo CSS in files the config's include does not cover✅
no-config-function-in-source (opens in a new tab)Prohibit config functions outside the Bamboo config file✅
no-debug (opens in a new tab)Disallow the debug attribute in shipped code✅
no-deprecated-tokens (opens in a new tab)Disallow deprecated tokens✅
no-descendant-selectors (opens in a new tab)Disallow nested selectors that style a different element than the class is applied to
no-dynamic-styling (opens in a new tab)Disallow dynamic styling; prefer static styles, CSS variables or recipes✅
no-escape-hatch (opens in a new tab)Prohibit escape hatch syntax
no-hardcoded-color (opens in a new tab)Use color tokens rather than hardcoded colors✅
no-important (opens in a new tab)Disallow !important
no-invalid-nesting (opens in a new tab)Nested styles must contain &✅
no-invalid-token-paths (opens in a new tab)Disallow token paths that do not exist, and values the build cannot resolve✅
no-margin-properties (opens in a new tab)Prefer gap in the parent to margins
no-physical-properties (opens in a new tab)Prefer logical properties to physical ones
no-property-renaming (opens in a new tab)Disallow renaming pattern and style props, which the build cannot follow✅
no-redundant-value (opens in a new tab)Report an edge or pair value written longer than it needs to be
no-unlayered-override (opens in a new tab)Disallow joining a class with one it cannot see, where neither can win by cascade layer✅
no-unresolved-styles (opens in a new tab)Report styles the build cannot read, whose declarations never reach the stylesheet✅
no-unsafe-token-fn-usage (opens in a new tab)Use the token directly where the token function is not needed✅
prefer-atomic-properties (opens in a new tab)Prefer atomic properties to composite ones
prefer-composite-properties (opens in a new tab)Prefer composite properties to atomic ones
prefer-longhand-properties (opens in a new tab)Prefer longhand properties to shorthands
prefer-shorthand-properties (opens in a new tab)Prefer shorthand properties to longhands
prefer-token (opens in a new tab)Prefer a token to a value written out that the token holds
prefer-unified-property-style (opens in a new tab)Disallow mixing atomic and composite forms of the same property
require-literal-token-path (opens in a new tab)Require token paths the build can resolve, so prune.tokens can drop the rest
require-recipe-class-name (opens in a new tab)Require a className on a recipe, so its class names do not depend on what the build could read✅