consistent-type-exports: offer the export * fix as a suggestion

`export *` loads the module; `export type *` is erased. The autofix
rewrote one into the other whenever a module exported only types, so a
module that registers something when loaded stopped being loaded (the
api typegen lost its Handlebars partial this way).

The report stays an error; the rewrite is now a suggestion, which --fix
does not apply. Named re-exports are unaffected: under
verbatimModuleSyntax their type-only form is already a compile error.
This commit is contained in:
Satoshi Qazi Muhammed committed 2026-10-10 01:05:35 +04:00
1 parent aa231ca6b9
commit e972491c5e
3 files changed
+146 -2

No files matched your search

+3 -2
View File
@@ -25,6 +25,7 @@ import globals from 'globals';
import { overrideAll, overrideJs, overrideJsx, overrideSpec } from './eslint.rules.js';
import { standardRules } from './eslint.standard.js';
import { withoutContextualAssertions } from './eslint.typeAssertion.js';
import { withoutExportAllFix } from './eslint.typeExports.js';
/**
* eslint-plugin-header declares no options schema, and eslint 9+ rejects
@@ -94,8 +95,8 @@ export default [
},
plugins: {
'@stylistic': stylisticPlugin,
// see eslint.typeAssertion.js
'@typescript-eslint': withoutContextualAssertions(tsPlugin),
// see eslint.typeAssertion.js and eslint.typeExports.js
'@typescript-eslint': withoutExportAllFix(withoutContextualAssertions(tsPlugin)),
// eslint-plugin-header reports at the first token after the leading
// comments, so a generated file's /* eslint-disable */ covers it; the
// maintained fork reports at line 1 instead. The compat layer supplies the
+63
View File
@@ -0,0 +1,63 @@
// Copyright 2017-2026 @pezkuwi/dev authors & contributors
// SPDX-License-Identifier: Apache-2.0
const RULE = 'consistent-type-exports';
/**
* consistent-type-exports reports an `export * from './x'` whose module only
* exports types, and its autofix turns it into `export type * from './x'`.
* That changes what runs: `export *` loads the module, `export type *` is
* erased, so a module that only exports types but registers something when it
* loads (a Handlebars partial, an interface augmentation) silently stops being
* loaded. Measured on the consumers: the api typegen lost its `docs` partial
* this way and the build failed only at template time.
*
* Named re-exports do not have this problem: under verbatimModuleSyntax a
* type re-exported without `type` is already a compile error (TS1205).
*
* This keeps the report as it is and moves the `export *` fix into a
* suggestion, so --fix no longer applies it and an editor still offers it.
* Whoever accepts it decides whether the module needs a side-effect import.
*
* @template {object} P
* @param {P} plugin the @typescript-eslint plugin
* @returns {P}
*/
export function withoutExportAllFix (plugin) {
const rules = /** @type {Record<string, import('eslint').Rule.RuleModule>} */ (/** @type {{ rules: unknown }} */ (plugin).rules);
const rule = rules[RULE];
return /** @type {P} */ ({
...plugin,
rules: {
...rules,
[RULE]: {
...rule,
/** @param {import('eslint').Rule.RuleContext} context */
create (context) {
const filtered = Object.create(context, {
report: {
value: (/** @type {import('eslint').Rule.ReportDescriptor} */ descriptor) => {
if ('node' in descriptor && descriptor.node.type === 'ExportAllDeclaration' && descriptor.fix) {
const { fix, ...rest } = descriptor;
context.report({
...rest,
suggest: [{ desc: 'Use `export type *` (the module is no longer loaded: add a side-effect import if it registers anything)', fix }]
});
return;
}
context.report(descriptor);
}
}
});
return rule.create(filtered);
},
meta: { ...rule.meta, hasSuggestions: true }
}
}
});
}