eslint-plugin-imports-regulation 0.2.2 → 0.2.4

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.
@@ -110,9 +110,33 @@ const rule = {
110
110
  const unmarkedTypeOnly = (node) => node.importKind === 'value'
111
111
  && node.specifiers.length > 0
112
112
  && node.specifiers.every(s => s.type === 'ImportSpecifier' && s.importKind === 'type');
113
+ /**
114
+ * Every binding is only used as a type, whether or not it says so yet.
115
+ *
116
+ * Memoised because `groupOf` asks once per import and the answer walks the scope chain.
117
+ */
118
+ const inferred = new Map();
119
+ const isInferredTypeOnly = (node) => {
120
+ const cached = inferred.get(node);
121
+ if (cached !== undefined)
122
+ return cached;
123
+ const typeOnly = typeOnlyBindings(node);
124
+ const result = node.specifiers.length > 0
125
+ && node.specifiers.every(specifier => typeOnly.has(specifier.local.name));
126
+ inferred.set(node, result);
127
+ return result;
128
+ };
113
129
  // `isTypeOnly`, not `typeOnly`: the option of that name is a spelling, this is a fact.
130
+ //
131
+ // 🚨 The inferred case has to count here, not just the written one. Classify by syntax alone
132
+ // and an import that is *about* to be marked type-only still sorts as a value import, so the
133
+ // order check fires on a classification that is one fix out of date and swaps two imports
134
+ // that both end up in the same group. Stable sorting then keeps them swapped, so the author
135
+ // loses their order for no reason and the file never settles back.
114
136
  const groupOf = (node) => {
115
- const isTypeOnly = node.importKind === 'type' || unmarkedTypeOnly(node);
137
+ const isTypeOnly = node.importKind === 'type'
138
+ || unmarkedTypeOnly(node)
139
+ || (inferTypeOnlyImports && isInferredTypeOnly(node));
116
140
  const base = originOf(node.source.value) === 'internal' ? INTERNAL_TYPE : EXTERNAL_TYPE;
117
141
  return isTypeOnly ? base : base + 1;
118
142
  };
@@ -173,9 +197,9 @@ const rule = {
173
197
  const typeOnly = typeOnlyBindings(node);
174
198
  if (typeOnly.size === 0)
175
199
  return false;
176
- const everyBinding = node.specifiers.length > 0
177
- && node.specifiers.every(specifier => typeOnly.has(specifier.local.name));
178
- if (everyBinding && preferTypeDeclarations) {
200
+ // Not gated on `preferTypeDeclarations`: that option is about leaving a spelling you
201
+ // wrote alone. Here there is no spelling yet, and `import type` is the one to write.
202
+ if (isInferredTypeOnly(node)) {
179
203
  const keyword = source.getFirstToken(node);
180
204
  if (!keyword)
181
205
  return false;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eslint-plugin-imports-regulation",
3
- "version": "0.2.2",
4
- "description": "Order imports: package type-only, package, blank line, local type-only, local — with type specifiers last inside each import.",
3
+ "version": "0.2.4",
4
+ "description": "Order imports: external type-only, external, blank line, internal type-only, internal — with type specifiers last inside each import.",
5
5
  "author": "Robert Sandiford",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -31,7 +31,7 @@
31
31
  "devDependencies": {
32
32
  "@types/node": "^22.19.15",
33
33
  "eslint": "^10.8.1",
34
- "typescript-eslint": "^8.67.0"
34
+ "typescript-eslint": "^8.70.0"
35
35
  },
36
36
  "scripts": {
37
37
  "compile": "tsc",