ultracite@8.2.0
Minor Changes
-
f971134: The ESLint
angularpreset now lints Angular templates, accessibility included, with@angular-eslint/eslint-plugin-template. Until now the preset only linted component classes, so a template got no checks on ESLint.Angular projects on ESLint will see new errors in templates. The preset covers every
.htmlfile and the inlinetemplateof each component in a.tsfile, where errors are reported at their place in the TypeScript file andultracite fixfixes them there. Thirty-nine rules are on aterror. The accessibility rules cover missingalttext, labels with no control, click and mouse handlers with no keyboard equivalent, handlers on elements that can’t take focus, empty headings, links and buttons, and invalid ARIA roles and attributes. The rest are Angular’s general template rules, such as@angular-eslint/template/prefer-control-flow,@angular-eslint/template/eqeqeq,@angular-eslint/template/button-has-typeand@angular-eslint/template/no-duplicate-attributes. Measured on three open-source Angular apps, the two rules that report most are@angular-eslint/template/attributes-orderand@angular-eslint/template/prefer-self-closing-tags, andultracite fixfixes both.@angular-eslint/template/no-autofocusis off, asjsx-a11y/no-autofocusis in the React presets. Autofocus is a deliberate UX choice (a waitlist or search field that’s ready to type into), and the rule can’t tell that from a harmful use.@angular-eslint/template/i18nis off. It only applies to apps translated with Angular’s own i18n, and wants ani18nattribute on every element with text and beside attributes such asrelanddata-testid.@angular-eslint/template/no-call-expressionis off. It reports every signal read (count()), which is how a template reads a signal.@angular-eslint/template/cyclomatic-complexityallows 20 control-flow blocks in a template, as core’scomplexityallows a function, in place of the plugin’s 5.@angular-eslint/template/no-duplicate-attributesaccepts a staticclassorstylebeside a binding to the same attribute, which Angular merges.@angular-eslint/template/no-inline-stylesaccepts a style bound to a computed value ([style.width.%]="progress()"), which can’t be a class. A staticstyleattribute is still reported.- The rules can’t see what a directive or component adds. A button whose label comes from a directive (
<button appIconButton></button>) is reported as empty by@angular-eslint/template/elements-content. List those attributes in the rule’sallowListoption in your own config.
Every
.htmlfile is linted as a template, because Angular no longer names templates*.component.html. That includes whole pages: files that start with a doctype, an XML declaration, or an<html>,<head>or<body>element, such assrc/index.html, a static page inpublic/or an email. A page gets the accessibility rules and the other template rules, apart from three that only fit a template Angular compiles:@angular-eslint/template/prefer-ngsrc,@angular-eslint/template/no-inline-stylesand@angular-eslint/template/prefer-self-closing-tags. The last one’s fix would turn<my-widget></my-widget>into<my-widget />, which a browser reads as an open tag. A{or}in a page’s text, such as in a code sample, is read as text. In any other HTML file it’s a parse error, as it is when Angular compiles the template. The inline scripts of an HTML file are linted by core’s rules, andgithub/filenames-match-regexchecks each HTML file’s name, as core does.To change a template rule, set it in a config object with
files: ["**/*.html"], which also matches inline templates. That pattern no longer reaches the inline scripts of an HTML file, which are linted as a block of their own: settings for them, such as extra browser globals, needfiles: ["**/*.html/*.htm"]. Core’s JavaScript rules are off for templates, since they can’t read a template’s syntax tree. A rule your config turns on for every file runs on templates too. Some report there:jsdoc/require-file-overview, for example, reports every template as missing@file. Others throw:github/js-class-name,github/unescaped-html-literalandsonarjs/code-evalthrow on a template literal in a template ({{ `Hi ${name}` }}). Give rules meant for your TypeScriptfilesthat leave out**/*.html.ultracite initinstalls@angular-eslint/eslint-plugin-templateand@angular-eslint/template-parserfor Angular projects that choose ESLint. Existing projects get both fromultracite upgrade, which installs every package the presets in your ESLint config load, andultracite doctorreports the plugin as missing until then. Oxlint doesn’t parse Angular templates, and Biome’s accessibility rules already run on.htmlfiles from its core preset, so neither changes. -
37ead86: Ultracite no longer sorts object keys by default.
sort-keysis off in the Oxlint and ESLint core presets, anduseSortedKeysis off in the Biome core preset, soultracite fixstops reordering your objects andultracite checkstops reporting unsorted ones.Both fixes are classed as safe, so they ran on every
ultracite fix, and key order can change what code does: the bytesJSON.stringifywrites, the order a loop visits keys, which entry of anexportsmap wins, and what TypeScript infers for an options object. The research post has the cases we measured on real codebases.To keep sorting, turn the rule back on in your own config:
- Oxlint and ESLint:
"sort-keys": "error"inrules. - Biome:
"useSortedKeys": "on"underassist.actions.source.
The TanStack presets no longer need their own exemption from key sorting and have dropped it. If you turn sorting back on in a TanStack project, turn it off again for your query, mutation and route option objects, where sorting breaks type inference.
Biome’s
useSortedSelectionSetis off too: the order of a GraphQL selection set is the key order of the response. Biome’s other sorting actions (imports, JSX attributes, interface members, CSS properties,package.jsonfields and Tailwind classes) are unchanged. - Oxlint and ESLint:
-
37ead86: Bring the three linters’ presets in line with each other on the rules the research posts measured. Oxlint is the benchmark, so most changes move ESLint and Biome to what Oxlint already did.
Stricter. These can report new problems when you upgrade.
- Biome:
noExplicitAnynow applies to test files too, asno-explicit-anydoes in Oxlint and ESLint. - Biome:
noImportCyclesmoves from thetype-awarepreset into core, where Oxlint and ESLint already check import cycles. It adds no measurable time, because core skipsnode_modules(#843). - Biome:
noExcessiveClassesPerFileis on, matchingmax-classes-per-filein Oxlint and ESLint. - ESLint: the
reactpreset now applies to every JavaScript and TypeScript file, not only.jsxand.tsx, so custom hooks in.tsfiles get thereact-hooksrules and JSX in.jsfiles gets the accessibility rules. Oxlint and Biome already checked both. Linting a non-React TypeScript project with thereactpreset loaded took about 30% longer in our measurement. - Oxlint: the
js-plugins,next/js-pluginsandtanstack/js-pluginspresets gain the 267 React Doctor rules the ESLint presets already ran, so both linters now run the same list. Most are for one library (React Router, three.js, Ink, Motion, Remotion) and stay silent in projects that don’t use it. The JS plugin pass took about 1.6 times as long on our test project. - ESLint and Oxlint
js-plugins:sonarjs/shorthand-property-groupingis on. It was off only because it conflicted withsort-keys, which is now off by default. - Oxlint and ESLint:
no-warning-commentsalso reports JSDoc’s@todotag and a marker behind the asterisks of a block comment (/** @todo */,/* ** TODO */).
Looser, or off.
no-unused-varsfollows one convention in all three linters: a leading underscore marks a parameter, variable, caught error or destructured array element as unused on purpose, and a property left out with a rest sibling (const { secret, ...rest } = obj) and ausingdeclaration aren’t reported. ESLint used to report all of these, and Oxlint reported a bare_and every unusedcatchbinding. This also stopsultracite fix --unsafeon Oxlint from deleting the omitted property, which put it back intorest.sonarjs/no-unused-varsis off in ESLint and Oxlintjs-plugins: it duplicates the rule and has no option for the convention.no-use-before-definesetsvariables: falsein Oxlint and ESLint: a function that mentions a variable declared further down in an outer scope is no longer reported, since the body runs later. Functions that call each other had no order that passed. A use in the same scope, such as a call at load time above the definition, still reports. The TanStack presets now keep the rule on in route files instead of turning it off, so a route that reads its component before the component is declared is reported.react/function-component-definitionis off in Oxlint and ESLint. Its fix turned a hoistedfunctioncomponent into aconstthat could be read before it was defined, and its fix for an unnamed component produced code the rule reported again.func-stylestill asks for function expressions, and leavesexport default function Page()alone.- Namespace imports are allowed everywhere:
sonarjs/no-wildcard-importis off in ESLint and Oxlintjs-plugins, andnoNamespaceImportis off in Biome, matchingimport/no-namespacein Oxlint core. Libraries such as Zod and Radix documentimport * asas their API. - Barrel files: Biome’s
noBarrelFile, ESLint’sunicorn/no-barrel-filesand React Doctor’sno-barrel-importare off. The first two report a package’s entry point, and React Doctor made its rule opt-in. Oxlint’soxc/no-barrel-file, which reports only very large barrels, stays on. - Biome:
noVoidis off, sovoid promise;can mark a promise that’s deliberately not awaited, as Oxlint and ESLint allow. - Oxlint:
unicorn/prefer-export-fromis off until its fix is corrected (oxc-project/oxc#29318). With--unsafeit could drop an export when oneexport { a, b }re-exported from two modules. ESLint keeps the rule. - ESLint:
jsx-a11y/prefer-tag-over-roleis off until jsx-eslint/eslint-plugin-jsx-a11y#1102 is fixed. It threw on an object-valuedroleprop, which aborted the whole run. - ESLint:
unicorn/name-replacementsis off. Its autofix renamed identifiers across a file (reqtorequest,envtoenvironment). - ESLint: these rules, which reported nothing or didn’t apply, are off:
import-x/dynamic-import-chunkname(webpack magic comments),unicorn/expiring-todo-comments,vue/max-lines-per-block,vue/max-template-depthandsvelte/max-lines-per-block. - ESLint and Oxlint
js-plugins:sonarjs/todo-tagandsonarjs/fixme-tagare off. They reported every TODO a second time, with a message that contradictedno-warning-comments. - ESLint:
jsx-a11y/control-has-associated-labeluses the options the plugin ships in its own configs, so an input its label wraps is no longer reported, and the Astro preset’slabel-has-associated-controltakes the same label and control components as the React preset.astro/jsx-a11y/no-autofocusis off, asno-autofocusalready was for React. - ESLint: five React Doctor rules are dropped: the four
jsx-no-*-as-proprules, which the Oxlint React preset turns off as idiomatic React, andno-ref-callback-cleanup-before-react-19, which reported valid React 19 code.
- Biome:
-
f971134: The Oxlint and ESLint
qwikpresets now lint Qwik’s JSX for accessibility, with the samejsx-a11yrules, severities and options as thereactpresets. Until now a Qwik project got no accessibility checks on either linter, because the rules lived in thereactpresets only.Qwik projects on Oxlint and ESLint will see new errors. Thirty-four rules are on at
errorin each, covering missingalttext, labels and controls that aren’t associated, anchors with nohref, invalid ARIA roles and attributes, media without captions, and more. Measured on two open-source Qwik codebases, the rules that report most arejsx-a11y/control-has-associated-label,jsx-a11y/label-has-associated-control(ESLint),jsx-a11y/alt-textandjsx-a11y/role-supports-aria-props, and on Oxlintjsx-a11y/prefer-tag-over-role.- The rules that look at event handlers don’t work on Qwik’s handlers yet.
jsx-a11y/click-events-have-key-events,jsx-a11y/mouse-events-have-key-events,jsx-a11y/interactive-supports-focus,jsx-a11y/no-static-element-interactionsandjsx-a11y/no-noninteractive-element-interactionsonly know React’s names, and Qwik’s end in$, so a<div onClick$={…}>with no keyboard handler isn’t reported by either linter. They’re on anyway: they report nothing wrong on$handlers, and they’ll start reporting when Oxlint andeslint-plugin-jsx-a11ylearn the names. On the codebases measured, that would be 46 more reports on ESLint and 37 on Oxlint. - Qwik writes
classandforwhere React writesclassNameandhtmlFor. The ESLint preset tells the label rules that a label’s target isfor, so<label for="email">beside<input id="email" />passes, and a label with no control is still reported. No rule readsclassName. - On Oxlint,
jsx-a11y/label-has-associated-controlis off. Oxlint only acceptsforthroughsettings["jsx-a11y"].attributes, and it doesn’t readsettingsfrom a preset (oxc-project/oxc#28047), so the rule would report every<label for="…">. To turn it on, set"jsx-a11y/label-has-associated-control": "error"inrulesand"jsx-a11y": { attributes: { for: ["htmlFor", "for"] } }insettingsin your ownoxlint.config.ts. - On ESLint, a
<Slot />isn’t counted as a label’s control the way{children}is in React, so a label component that renders<label {...props}><Slot /></label>is reported byjsx-a11y/label-has-associated-control. Give the label afor, or disable the rule on that line. jsx-a11y/no-autofocusis off, as it is in the React presets, and on ESLint so isjsx-a11y/prefer-tag-over-role, which crashes on an object-valuedrole(jsx-eslint/eslint-plugin-jsx-a11y#1102).jsx-a11y/anchor-is-validandqwik/jsx-aboth report an<a>with nohrefon ESLint.
ultracite initinstallseslint-plugin-jsx-a11yfor Qwik projects that choose ESLint. Existing projects get it fromultracite upgrade, which installs every plugin the presets in your ESLint config load, andultracite doctorreports it as missing until then. Oxlint’s rules are built in, so there’s nothing to install. Biome’s accessibility rules already run from its core preset, so it doesn’t change. - The rules that look at event handlers don’t work on Qwik’s handlers yet.
-
f971134: The Oxlint and ESLint
solidpresets now lint Solid’s JSX for accessibility, with the samejsx-a11yrules, severities and options as thereactpresets. Until now a Solid project got no accessibility checks on either linter, because the rules lived in thereactpresets only.Solid projects on Oxlint and ESLint will see new errors. Thirty-four rules are on at
errorin each, covering missingalttext, labels and controls that aren’t associated, click and mouse handlers with no keyboard equivalent, handlers on non-interactive elements, invalid ARIA roles and attributes, media without captions, and more. Measured on three open-source Solid codebases, the rules that report most arejsx-a11y/no-static-element-interactions,jsx-a11y/control-has-associated-label,jsx-a11y/click-events-have-key-eventsandjsx-a11y/alt-text, and on Oxlintjsx-a11y/prefer-tag-over-role.- Solid writes
classandforwhere React writesclassNameandhtmlFor. The ESLint preset tells the label rules that a label’s target isfor, so<label for="email">beside<input id="email" />passes, and a label with no control is still reported. No rule readsclassName. - On Oxlint,
jsx-a11y/label-has-associated-controlis off. Oxlint only acceptsforthroughsettings["jsx-a11y"].attributes, and it doesn’t readsettingsfrom a preset (oxc-project/oxc#28047), so the rule would report every<label for="…">. To turn it on, set"jsx-a11y/label-has-associated-control": "error"inrulesand"jsx-a11y": { attributes: { for: ["htmlFor", "for"] } }insettingsin your ownoxlint.config.ts. - The rules that look at event handlers know
onClick, and on ESLintonclick. They don’t see Solid’son:click, or on Oxlint lowercaseonclick, so an element that only has a handler in those forms isn’t checked for a keyboard equivalent. An element that mixes the forms, such asonClickwithon:keydown, is reported as having no key handler. - The rules can’t see content set through
innerHTMLortextContent, so<h1 innerHTML={title} />is reported as an empty heading, as an element React fills withdangerouslySetInnerHTMLis not. jsx-a11y/no-autofocusis off, as it is in the React presets, and on ESLint so isjsx-a11y/prefer-tag-over-role, which crashes on an object-valuedrole(jsx-eslint/eslint-plugin-jsx-a11y#1102).
ultracite initinstallseslint-plugin-jsx-a11yfor Solid projects that choose ESLint. Existing projects get it fromultracite upgrade, which installs every plugin the presets in your ESLint config load, andultracite doctorreports it as missing until then. Oxlint’s rules are built in, so there’s nothing to install. Biome’s accessibility rules already run from its core preset, so it doesn’t change. - Solid writes
-
f971134: The ESLint
vuepreset now lints Vue templates for accessibility witheslint-plugin-vuejs-accessibility. Until now a.vuetemplate got no accessibility checks on ESLint, becauseeslint-plugin-vuehas none.Vue projects on ESLint will see new errors. Twenty-one rules are on at
error, covering missingalttext, form controls and labels that aren’t associated, click and mouse handlers with no keyboard equivalent, handlers on non-interactive elements, invalid ARIA roles and attributes, media without captions, and more. Measured on three open-source Vue 3 apps, the rules that report most arevuejs-accessibility/no-static-element-interactions,vuejs-accessibility/label-has-for,vuejs-accessibility/click-events-have-key-eventsandvuejs-accessibility/form-control-has-label.vuejs-accessibility/no-autofocusis off, asjsx-a11y/no-autofocusis in the React presets. Autofocus is a deliberate UX choice (a waitlist or search field that’s ready to type into), and the rule can’t tell that from a harmful use.vuejs-accessibility/label-has-foraccepts a label that either wraps its control or points at it withfor. The plugin’s default asks for both, which reports correct markup. It also counts<Checkbox>and<Radio>components as controls, and a<slot />inside a label as one.- The rules can’t see inside components. A heading or link whose only content is a component (
<h1><PageTitle /></h1>) is reported as empty byvuejs-accessibility/heading-has-contentandvuejs-accessibility/anchor-has-content. List those components in the rules’accessibleChildrenoption in your own config.
ultracite initinstallseslint-plugin-vuejs-accessibilityfor Vue projects that choose ESLint. Existing projects get it fromultracite upgrade, which installs every plugin the presets in your ESLint config load, andultracite doctorreports it as missing until then. Oxlint doesn’t lint Vue templates, and Biome’s accessibility rules already run from its core preset, so neither changes.aria-rolenow checks DOM elements only, in the React presets of all three linters as well as the new Vue rules: aroleprop on a component, such as<Message role="assistant">, is no longer reported as an invalid ARIA role.
Patch Changes
-
8be2267:
ultracite fix --claudeand--codexnow recognise an issue that’s still reported after the agent’s edit when a limit rule’s message changed only by its count, when a message changed only by the line it points at, or when the same rule reports several times with the same message.- A limit rule such as
max-lines-per-function,max-statements,max-paramsorcomplexityputs the count in its message (“too many lines (88)”). A function the agent cut from 88 lines to 55 was counted as one issue fixed and one new issue, and the retry told the agent its edit had “resolved the issues it was given”. It’s now the same issue, still reported. Only the limit rules (themax-*and complexity rules, and Biome’snoExcessive*anduseMax*) are matched without their numbers, so ano-magic-numbersreport for100isn’t taken for the42the agent fixed. Other rules ignore only a line or column a message points at, or a count of repeats (no-shadow’s “declared in the upper scope on line 3”,sonarjs/no-duplicate-string’s “duplicating this literal 3 times”), since an edit anywhere in the file changes those. A report whose message changed only by some other number, such asimport/no-cycle’s “Dependency cycle via ./b:3”, is still the same issue when it’s on the original’s line and the edit left that line as it was. - Rules such as
typescript/no-explicit-anygive every report the same message. When an edit added new reports above the original ones, the retry called the agent’s new ones “still reported” and pointed at the untouched originals as “new since the previous edit”. Reports are now matched by the line of code they’re on first, and then by where the edit left an original’s line, between the nearest unchanged lines around it. A report there is the original, rewritten. An original whose surroundings are still there with no report among them counts as fixed, so a report the edit added elsewhere is new. Reports whose place can’t be told, such as code the edit moved, or a limit rule likemax-lines, which reports where its count runs out, are matched in file order as before. - Each agent is told to edit only its own file, but one can change another anyway (by running a formatter, say). A later file’s issues are now found again where that edit left them, so the ones it cleared count as fixed, and if re-linting the file fails, the agent is pointed at the lines its issues are on now.
- A limit rule such as
-
1341a85:
ultracite fix --claudeand--codexnow show what the agent said, and give it better instructions for the cases where the obvious edit is the wrong one.- The end of the agent’s final message is printed under each file. It used to be shown only when the agent left an issue for you, so a note such as “this looks like a real bug, I left it alone” never reached you.
- When the agent leaves issues for you, the summary says so: “Fixed 3 of 5 issues with Claude Code. 2 left for you to decide.” The command still exits non-zero for them, as
ultracite checkwould. - The prompt no longer tells the agent to preserve runtime behavior for every rule. Style rules keep that instruction, but when a rule reports a bug, such as a promise nobody awaits, fixing the behavior is the point. If a fix would change behavior that may be intended, the agent leaves the issue for you and says why.
- The prompt names edits that only hide code from a rule (rewording a comment, re-spelling an identifier, an equivalent construct) as not a fix, and says that leaving an issue is a valid result when the rule’s help text doesn’t fit the code or the proper fix is in another file. It also tells the agent not to run the linter itself, since the file is re-linted after every attempt.
- Each attempt gets 10 minutes instead of 5. The slowest correct fixes in our research runs took between five and six.
- With Oxlint’s
js-pluginspreset, rules fromeslint-plugin-sonarjs,eslint-plugin-githubandoxlint-plugin-react-doctornow reach the agent with the rule’s description and documentation URL, read from the plugin installed where the Oxlint config that lints the file loads it from. Oxlint reports neither for JS-plugin rules.
-
ece41c0: The rules
ultracite initwrites for agents (AGENTS.md,CLAUDE.mdand the other rules files) and the skill’s code standards now match what the presets enforce, and replace advice that led agents to the cheapest edit instead of the right one:- Accessibility has its own section instead of sitting under “React & JSX”, since it applies to every framework. It now leads with “Use semantic HTML first; add ARIA only when no native element fits”, and says to make anything clickable a
<button>or a link rather than adding key handlers to a<div>. - Hooks: “When a hook dependency rule fires, first check whether the effect is needed, and whether adding the dependency changes when it runs” replaces “Specify all dependencies in hook dependency arrays correctly”.
- Functions: “Write functions as arrow functions assigned to
const, and define them above where they’re used”, which is whatfunc-styleandno-use-before-defineask for. - TODOs: a new line says not to leave TODO or FIXME comments in new code, and never to reword an existing one to get past the linter.
- Imports: “Prefer specific imports over namespace imports” is gone, and the barrel file line now says a package’s entry point is fine.
Existing projects get the new rules by re-running
ultracite init. - Accessibility has its own section instead of sitting under “React & JSX”, since it applies to every framework. It now leads with “Use semantic HTML first; add ARIA only when no native element fits”, and says to make anything clickable a
-
7a2d831: Turn off
unicorn/consistent-arrow-return-stylein the ESLint core preset. It conflicts witharrow-body-style: a multi-line arrow function that only returns a value was reported by the Unicorn rule when written with a concise body and byarrow-body-stylewhen written with a block, so no form passed, andultracite fixswitched between the two without settling.arrow-body-stylestays on, as in the Oxlint preset.func-styledrops itsallowArrowFunctions: trueoption in the Oxlint and ESLint core presets. The option does nothing with the"expression"style, so nothing is reported differently, and ESLint plans to reject the combination in its next major (eslint/eslint#20689). -
15fdb0c:
ultracite doctornow checks the versions of the lint plugins your config loads, not only the linter and formatter. Each rule plugin of the Ultracite presets youreslint.config.*imports, and each Oxlint JS plugin youroxlint.config.tsloads, has to satisfy the range this release installs it at. A plugin that’s older than that range, or that your config loads but isn’t installed, fails the check and points atultracite upgrade, and one that’s newer warns. Plugins your config doesn’t load are left alone, even when another package (such as thereact-doctorCLI) installed them.This catches a crash that
doctorused to miss. The ESLint React preset andultracite/oxlint/js-pluginsturn onreact-doctor/forwarded-set-state-in-effect, which React Doctor added in 0.9.21, so a project that bumped onlyultraciteand kept an older React Doctor plugin saw ESLint stop withCould not find "forwarded-set-state-in-effect" in plugin "react-doctor"and Oxlint withRule 'forwarded-set-state-in-effect' not found in plugin 'react-doctor', whiledoctorpassed. Foreslint-plugin-react-doctor,doctoralso checks theoxlint-plugin-react-doctorit takes its rules from, which only differs when an override or resolution pins it.ultracite upgradenow also reinstalls the Oxlint JS plugins your config loads whenpackage.jsondoesn’t list them, so it can fix every plugindoctorflags. -
ece41c0:
ultracite doctornow warns when anoxlint.config.tsextends the core preset without settingenv, andultracite upgradeadds it. Oxlint doesn’t inheritenvthroughextends(oxc-project/oxc#20087), so a configinitwrote before 8.0.1 lints without the browser and Node globals, andno-implied-evalmissessetTimeout("code")andno-evalmisseswindow.eval, with nothing to say so.upgrademakes the change through the same updateinitruns, so the rest of the config is kept as written. A project that still has an.oxlintrc.json, or a configinitcan’t update, is left forultracite init, andupgradeonly says it addedenvwhen it did. When the config takes core through a name of its own (const base = { ...core }), imports a preset withimport * as, or declares a name the Ultracite config also declares (its ownjsPluginSettings, say),doctorsays to addenvby hand and whyupgradecan’t. When the config is in a parent directory, it says where to runupgrade. A config that spreads in another object (...shared) may setenvthrough it, sodoctoralso says it can’t tell. A configdoctorcan’t read, or one the Oxlint configuration check fails on (next to another Oxlint config, or a.tsconfig in a CommonJS package), gets noenvcheck rather than a pass.ultracite initandupgradeno longer move a spread in an Oxlint config.initwritesenv,extends,ignorePatterns,jsPluginsandsettingsahead of everything it carries over, so a spread that came before one of them ended up after it and overrode it. Such a config is now left unchanged, with a warning that says what to move. A spread after those properties stays where it is, and a propertyinitadds goes ahead of it, so whatever the spread sets still wins.initalso no longer says it updated the Oxlint config when it left it unchanged. -
42f5b93:
ultracite initnow extends the Oxlint presets in a fixed order when it updates an existingoxlint.config.ts:corefirst, then the framework presets, andanti-sloplast. It used to follow the order of the preset imports, so onceultracite fixhad sorted them, re-runninginitputantiSlop(and presets such asangularandastro) ahead ofcore, which turned back on the rules Anti-Slop turns off. Configs that were reordered this way are put back in order, andinitnow says when it moves a preset. A js-plugins selection now sits where the full js-plugins preset would, ahead of the react-doctor add-ons,shadcnandanti-slop, so Anti-Slop is always last.Updating an existing
oxlint.config.tsalso keeps more of what you wrote:- Which presets are extended now comes from
extendsrather than the imports, so a preset you import without extending (to reuse itsrules, say) keeps its import and is no longer added toextends. A full js-plugins preset that a new selection replaces inextends, or that an older release’s generated selection was built from, keeps its import only when your own code still uses it; a property key, comment, string, parameter or local const with its name doesn’t count. Workspace configs andultracite doctorgo byextendsas well, so a preset that’s only imported doesn’t count as loaded, while one your ownextendsentry takes whole (a const that is or spreads it, orjp.defaultforimport * as jp) does. Reading a property off it, as{ rules: jsPlugins.rules }does, doesn’t count. - A preset imported under another name, such as
import ultraciteCore from "ultracite/oxlint/core", keeps that name, including in the settingsinitmoves over from.oxlintrc.json, and a second import of the same preset under another name is kept as written. A presetinitadds takes anultracite-prefixed name, such asultraciteReact, when the config already uses the usual one. - Your own
extendsentries stay where they were among the presets: an entry beforecorestays first, one right after a preset stays after it, and one after the last preset stays last. An entry is no longer dropped because its name matches a constant older Ultracite releases generated, such asselectedJsPlugins, or because it’s ajsPluginsconst declared with aselectJsPluginsof your own, and aselectJsPluginsof your own is no longer read as an Ultracite selection. - A config that imports a preset with
import * as, or declares its owndefineConfig,selectJsPluginsorjsPluginSettingswhere the Ultracite config needs that name, is left unchanged with a warning that says how to fix it, instead of being rewritten into one that doesn’t load.
- Which presets are extended now comes from