Configuration
utoo-lint selects one config from the current working directory or its
ancestors, then applies rule-related CLI overrides.
The canonical config names are:
utlint.config.ts: a typed, executable config loaded by the npm/Node CLI.utlint.config.json: a static JSON config readable by both the npm CLI and the raw native binary.
Choose the format based on how the config will be used:
| Config | Best for | npm/Node CLI | Raw native binary |
|---|---|---|---|
utlint.config.ts | Type checking while authoring, imports, shared presets, and computed values | Yes | No |
utlint.config.json | Static configuration, schema validation, and direct native use | Yes | Yes |
These are alternative representations of the same active config. They are not implicitly merged. For the npm/Node entry point, discovery is nearest-first: all supported names are checked in one directory before the parent directory is considered. In the same directory, the order is:
utlint.config.tsutlint.config.jsonutoo.json(deprecated)utoo-lint.json(deprecated)
This means a nearer utlint.config.json wins over a more distant
utlint.config.ts. The two legacy JSON names remain temporarily supported for
migration, but new projects should use a canonical name.
The npm CLI discovers either canonical format automatically:
npx utoo-lint src
To select a file explicitly, pass either format to --config:
npx utoo-lint --config=utlint.config.ts srcnpx utoo-lint --config=utlint.config.json src
Use --no-config to ignore local config. Rule-related CLI options such as
--rules and individual rule toggles are applied after the selected config.
The resolved rules map is the complete enabled rule set, matching ESLint's
configuration model: omitted rules are disabled. If no config is selected,
utoo-lint uses its built-in default rules.
Project-config files and ignores patterns are resolved relative to the
selected config file's directory, including when that config is discovered in
an ancestor directory or selected explicitly.
TypeScript Config
Use utlint.config.ts for typed authoring, TypeScript syntax, imports, or
computed configuration:
import { defineConfig } from "@utoo/lint/config";export default defineConfig({files: ["src/**/*.{js,jsx,ts,tsx}"],ignores: ["dist", "node_modules"],rules: {"no-debugger": "error","no-console": "warn"}});
The packaged frontend preset has a typed export and can be extended directly without copying its JSON file or asserting its rule types:
import { defineConfig } from "@utoo/lint/config";import frontend from "@utoo/lint/configs/frontend";export default defineConfig({...frontend,ignores: [...frontend.ignores, ".next", "storybook-static"],rules: {...frontend.rules,"no-console": "off",},});
Global and Entry-Scoped Ignores
Use globalIgnores() to exclude files or whole directories from every config
entry. The helper returns an ignore-only entry, so pass it as a separate
defineConfig() argument or array item:
import { defineConfig, globalIgnores } from "@utoo/lint/config";export default defineConfig(globalIgnores(["dist/", ".next/", "**/generated/"]),{files: ["**/*.{js,jsx,ts,tsx}"],rules: {"no-debugger": "error"}});
An entry is global only when it contains ignores and, optionally, name.
Adding files, rules, or another config key makes its ignores patterns
entry-scoped: they stop that entry from applying to matching files, but do not
exclude those files from other entries. Only global ignores prune matching
directories during file discovery. A trailing slash expresses a directory;
use dist/ or .next/ for a directory beside the config and
**/generated/ for directories with that name at any depth.
When no lint target is passed, the npm/Node wrapper discovers targets from the
config's files patterns, or scans the current directory if there are none.
Global ignores filter that discovery and prevent traversal into ignored
directories. Entry-scoped ignores are evaluated later, while resolving the
matching config for each discovered file. This shape intentionally follows
ESLint flat config's global and non-global ignore semantics.
defineConfig() accepts config objects and flat config arrays and returns a
flat array. It also provides editor completion and compile-time checking from
the exported configuration types. A TypeScript config is trusted executable
code: loading it can run arbitrary code with the permissions of the Node
process. Its default export must be a JSON-serializable config object or flat
config array. The loader transpiles and executes TypeScript syntax; it does not
run tsc or perform type checking when linting starts.
For a flat config array, each entry's files and ignores select the files to
which that entry applies. Matching entries are combined in array order, so a
later matching rule value overrides an earlier value for the same rule. The npm
CLI, JavaScript API, and fishlint compatibility command apply this rule
resolution separately for each linted file.
The npm wrapper executes the TypeScript file, materializes its result as JSON,
and passes that flat config to one native process. The raw native binary does
not execute or discover TypeScript; it searches for utlint.config.json and
then the legacy JSON names. Use the npm CLI for utlint.config.ts; when
invoking the raw binary directly, use utlint.config.json.
The raw binary resolves static flat-config files, global and entry-scoped
ignores, ordered rule overrides, and supported shared settings for each
file. With no explicit lint target, config selectors and global ignores also
control native file discovery. Patterns are relative to the selected config's
directory.
JSON Config
Use utlint.config.json for a static, runtime-independent config:
{"$schema": "https://raw.githubusercontent.com/utooland/utoo-lint/main/npm/utoo-lint/schema.json","files": ["src/**/*.{js,jsx,ts,tsx}"],"ignores": ["dist", "node_modules"],"rules": {"no-debugger": "error","no-console": "warn"}}
The optional $schema property enables completion and validation in editors
that support JSON Schema. JSON config cannot contain imports or computed
values; use utlint.config.ts when those are required.
Version-aware Jest rules detect the closest installed jest package by
default and fall back to the latest supported major when no installation can
be found. Set settings.jest.version when a repository uses multiple Jest
versions or needs to pin the version used for linting:
{"settings": {"jest": {"version": "29.7.0","globalAliases": { "describe": ["context"] }}},"rules": {"jest/no-deprecated-functions": "error","jest/no-focused-tests": "error"}}
Use settings.jest.globalAliases to map canonical Jest functions to
project-specific global names. For example, the configuration above treats
context as an alias for describe.
Declare project globals with languageOptions.globals, using the same values
as ESLint flat config. no-undef accepts declared names, no-global-assign
reports writes to readonly globals and allows writes to writable ones, and
"off" removes a global, including a built-in one:
{"languageOptions": {"globals": {"APP_VERSION": "readonly","__DEV__": "writable","window": "off"}},"rules": {"no-undef": "error","no-global-assign": "error"}}
In a flat config array, every entry that matches a file contributes its
globals, and later entries override earlier ones per name. The ESLint-compatible
JavaScript API also forwards eslintrc-style globals to the native rules.
ESLint config files are a migration input, not the recommended long-term configuration format. Generate a native config with:
npx utoo-lint migrate eslint --from eslint.config.js --output utlint.config.json
Frontend Project Config
For a React or TypeScript frontend project, start by copying the packaged frontend template into the project root:
cp node_modules/@utoo/lint/configs/frontend.json utlint.config.jsonnpx utoo-lint
The template includes a $schema entry for editor validation and a focused set of browser, import, React, JSX a11y, and TypeScript rules:
{"$schema": "https://raw.githubusercontent.com/utooland/utoo-lint/main/npm/utoo-lint/schema.json","files": ["src/**/*.{js,jsx,ts,tsx}"],"ignores": ["dist", "coverage", "node_modules"],"rules": {"no-debugger": "error","no-console": "warn","react/jsx-no-target-blank": "error","jsx-a11y/aria-props": "error","@typescript-eslint/no-unused-vars": "warn"}}
With no target argument, the copied preset scans JavaScript and TypeScript
under src. Its rules do not apply to dist, coverage, or node_modules,
including when . is passed explicitly. Add framework-specific generated
directories such as .next, storybook-static, or build to the copied
ignores array. Typed configs can append to frontend.ignores as shown in the
TypeScript example above.
utoo-lint treats config severities like ESLint. off, 0, and false
disable a rule; warn, warning, on, and 1 report warnings; error, 2,
and true report errors. ESLint-style arrays are accepted; the first item
controls severity, and supported option objects are passed to the native rule
implementation. Warnings leave the process exit status at 0, while errors
return status 1.
Rule Names
Use canonical ESLint rule names in rules:
{"rules": {"no-debugger": "error","import/no-duplicates": "error","react/jsx-no-duplicate-props": "error","jsx-a11y/iframe-has-title": "error","@typescript-eslint/no-require-imports": "error"}}
Unknown rule names are rejected so typos do not silently pass. See Rule status for the implemented rule list.
ESLint Plugins
utoo-lint can run community ESLint plugins alongside its native rules. Mount
the plugin under a namespace in a TypeScript or JavaScript config module and
enable its rules as <namespace>/<rule>:
import { defineConfig } from "@utoo/lint/config";import unicorn from "eslint-plugin-unicorn";export default defineConfig({files: ["**/*.{js,jsx,ts,tsx}"],plugins: { unicorn },rules: {"no-debugger": "error","unicorn/prefer-string-slice": "error","unicorn/catch-error-name": ["warn", { name: "err" }]}});
Plugin diagnostics land in the same report as native diagnostics, ordered by
position, in the CLI, fishlint, and the JavaScript API (lintFiles,
lintText, ESLint, Linter, and RuleTester). eslint-disable comments and
utlint-ignore suppress plugin rules, and plugin autofixes and suggestions are
applied by --fix and reported by --fix-dry-run.
How it works:
- The native engine lints every file first. Rules provided by a plugin never reach the native binary, so they do not need a native implementation.
- Each file with plugin rules enabled is parsed again in Node.js with
yuku, the parser the native engine is built on, into an ESTree / TypeScript-ESTree tree. The rules run against that tree with an ESLint-compatiblecontextandsourceCode. - Plugin objects are loaded from the config module in-process. Plugins must
therefore be mounted from
utlint.config.ts(or.js,.mjs,.cjs,.mts,.cts); JSON configs cannot mount plugins.
Native rules take precedence. When a mounted plugin provides a rule that
utoo-lint implements natively, such as react/jsx-key from
eslint-plugin-react, the native implementation runs and the plugin copy is
skipped. Only rule IDs without a native implementation are routed to the
plugin, so a config copied from ESLint keeps its native speed. See
Rule status for the native list.
Supported rule APIs:
context.reportwithmessageormessageId,data,loc,fix, andsuggest.context.options, includingmeta.defaultOptionsand JSON-schemadefaultvalues for provided option objects.context.settings,context.languageOptions(sourceType,ecmaVersion,globals,parserOptions.ecmaFeatures),context.filename, andcontext.cwd.- esquery selectors, including
:exit, attribute, child, descendant,:has,:matches, and:not. sourceCodetext, lines, tokens, comments,getAncestors, and scope analysis (getScope,getDeclaredVariables,markVariableAsUsed) backed byeslint-scopefor JavaScript files and@typescript-eslint/scope-managerfor TypeScript files. ECMAScript built-ins, CommonJS globals, andlanguageOptions.globalsare declared in the global scope.
Not supported: sourceCode.parserServices (type-aware rules), a custom
languageOptions.parser, processor, and code-path analysis events such as
onCodePathStart. A rule that depends on one of these loads without an error
but reports nothing for that feature.
Plugin rules run in JavaScript, so they are slower than native rules. Keep them
scoped with files to the sources that need them. Running plugin rules requires
Node.js 20.19 or newer (or 22.12 or newer).
CLI Precedence
Rule-related CLI options are applied after the config file:
npx utoo-lint --config=utlint.config.json --no-console=off src
For one-off focused runs, --rules disables every rule first, then enables only the listed rules:
npx utoo-lint --rules=no-debugger,react/jsx-no-target-blank src
React Compiler-related checks
Enable the native React Compiler-related checks explicitly in utlint.config.json:
{"rules": {"react-hooks/use-memo": "error","react-hooks/void-use-memo": "error","react-hooks/set-state-in-render": "error","react-hooks/purity": "error"}}
These rules are disabled by default and are not added to the frontend preset.
They run in both native and WebAssembly builds without installing React Compiler.
They cover a documented subset of the upstream rules; enabling them does not prove
that a component can be compiled. See rule status
for supported patterns and limitations.
Rule names follow React main at e92bda78750136493cb324e98df1726f62ba8e92: use-memo
checks callback signatures and captured assignments; void-use-memo checks missing
returns and unused results and is in upstream recommended-latest. The selected
upstream fixtures and their coverage boundaries are recorded in each rule’s
tests/snapshots/specs/react-hooks/<rule>/UPSTREAM.md.