@blumintinc/eslint-plugin-blumint 1.20.131 → 1.20.132

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -223,7 +223,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
223
223
  module.exports = {
224
224
  meta: {
225
225
  name: '@blumintinc/eslint-plugin-blumint',
226
- version: '1.20.131',
226
+ version: '1.20.132',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -1,2 +1,4 @@
1
1
  import { TSESLint } from '@typescript-eslint/utils';
2
- export declare const preferTypeOverInterface: TSESLint.RuleModule<'preferType', never[]>;
2
+ type MessageIds = 'preferType' | 'preferTypeDefaultExport';
3
+ export declare const preferTypeOverInterface: TSESLint.RuleModule<MessageIds, never[]>;
4
+ export {};
@@ -47,6 +47,21 @@ function isInsideModuleAugmentation(node) {
47
47
  }
48
48
  return false;
49
49
  }
50
+ /**
51
+ * `export default interface X {…}` is the only spelling TypeScript has for a
52
+ * default-exported type: `export default type X = …` is a syntax error in every
53
+ * form, so the keyword swap has no landing site. It lands there anyway because
54
+ * the `TSInterfaceDeclaration`'s range starts at `interface` while
55
+ * `export default` belongs to the parent node, which made `eslint --fix` write
56
+ * a file that no longer parses (#1850).
57
+ *
58
+ * The conversion is still available to the author, as a two-statement
59
+ * restructure a keyword swap cannot express, so the report stands and carries
60
+ * that remedy instead of a fix.
61
+ */
62
+ function isDefaultExported(node) {
63
+ return node.parent?.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration;
64
+ }
50
65
  /**
51
66
  * Declaration merging is what `interface` can do and `type` cannot, so a name
52
67
  * carrying more than one declaration is not a stylistic choice: rewriting one
@@ -92,6 +107,10 @@ exports.preferTypeOverInterface = (0, createRule_1.createRule)({
92
107
  preferType: 'Interface "{{interfaceName}}" should be declared as a type alias. ' +
93
108
  'Interfaces can merge across declarations and extend in chains, which fragments the resulting shape across files and makes composition harder to predict and trace. ' +
94
109
  'Replace `interface` with `type` and use intersections (for example, `type {{interfaceName}} = Base & { field: string }`) to keep the contract closed and predictable.',
110
+ preferTypeDefaultExport: 'Interface "{{interfaceName}}" should be declared as a type alias, but a default-exported interface has no in-place conversion, so no autofix is offered here. ' +
111
+ 'Interfaces can merge across declarations and extend in chains, which fragments the resulting shape across files and makes composition harder to predict and trace. ' +
112
+ 'TypeScript has no default-exported type alias — `export default type {{interfaceName}} = ...` is a syntax error — so the conversion takes two statements: `type {{interfaceName}} = { field: string };` followed by `export type { {{interfaceName}} as default };`, which keeps every existing `import {{interfaceName}} from ...` working. ' +
113
+ 'A named `export type {{interfaceName}} = ...` works too, once the import sites are switched to `import type { {{interfaceName}} }`.',
95
114
  },
96
115
  fixable: 'code',
97
116
  },
@@ -108,6 +127,22 @@ exports.preferTypeOverInterface = (0, createRule_1.createRule)({
108
127
  if (isMergedDeclaration(context, node)) {
109
128
  return;
110
129
  }
130
+ // Withholding the fix rather than emitting a broken one: every rewrite
131
+ // below assumes the `interface` keyword can become `type` where it
132
+ // stands, and under `export default` that position accepts no
133
+ // declaration at all. The report is kept because the author *can* act
134
+ // on it — unlike a merge, the shape converts by hand — and the remedy
135
+ // travels in the message.
136
+ if (isDefaultExported(node)) {
137
+ context.report({
138
+ node,
139
+ messageId: 'preferTypeDefaultExport',
140
+ data: {
141
+ interfaceName: node.id.name,
142
+ },
143
+ });
144
+ return;
145
+ }
111
146
  context.report({
112
147
  node,
113
148
  messageId: 'preferType',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.20.131",
3
+ "version": "1.20.132",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -1,4 +1,18 @@
1
1
  [
2
+ {
3
+ "version": "1.20.132",
4
+ "date": "2026-08-07T12:26:09.881Z",
5
+ "rules": [
6
+ {
7
+ "name": "prefer-type-over-interface",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 1850
11
+ ],
12
+ "summary": "decline the fix for a default-exported interface (closes #1850)"
13
+ }
14
+ ]
15
+ },
2
16
  {
3
17
  "version": "1.20.131",
4
18
  "date": "2026-08-07T10:28:22.079Z",