@exadev/eslint-config 2.10.2 → 2.10.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.
package/README.md CHANGED
@@ -113,6 +113,8 @@ export default tseslint.config(
113
113
  If none of these resolve, `@exadev/eslint-config`'s default export is byte-for-byte identical to the plain TypeScript ruleset -- nothing about the base package changes.
114
114
  2. **For React specifically, the file must actually be `.jsx`/`.tsx`.** The React/hooks/a11y rule block is scoped to `files: ['**/*.jsx', '**/*.tsx']`, so even if `eslint-plugin-react` is resolvable only incidentally (e.g. hoisted as a transitive dependency of something unrelated in a monorepo, with zero real JSX anywhere in the linted project), its rules are never matched against a file that isn't JSX -- ESLint's flat-config `files` matching happens per linted file, at lint time, not at config-build time. `@next/eslint-plugin-next`'s block carries no such glob: its own presence is already an unambiguous signal on its own (nothing installs it except a real Next.js project).
115
115
 
116
+ React support pairs `eslint-plugin-react`'s `flat/recommended` with its own `flat/jsx-runtime` config, which turns `react/react-in-jsx-scope` and `react/jsx-uses-react` back off. `flat/recommended` alone assumes the classic runtime, where every file using JSX needs `import React` in scope; the automatic JSX runtime, the default since React 17 and the only mode Next.js's own compiler supports, needs no such import. Without this pairing, a consumer on the automatic runtime would see `react/react-in-jsx-scope` fire on every JSX file in the project.
117
+
116
118
  ### Explicit control
117
119
 
118
120
  Two ways to override the automatic behaviour, for anyone who doesn't want to rely on it:
package/dist/index.cjs CHANGED
@@ -89,16 +89,23 @@ function isFlatConfig(value) {
89
89
  }
90
90
  function buildReactConfig(options = {}) {
91
91
  if (options.enabled === false) return [];
92
- const reactConfig = readFlatConfig(tryRequire("eslint-plugin-react", options.requireFn), [
92
+ const reactModule = tryRequire("eslint-plugin-react", options.requireFn);
93
+ const reactConfig = readFlatConfig(reactModule, [
93
94
  "configs",
94
95
  "flat",
95
96
  "recommended"
96
97
  ]);
97
98
  if (options.enabled === true && reactConfig === void 0) throw new Error(`@exadev/eslint-config: React support was explicitly requested but 'eslint-plugin-react' could not be resolved. Install it with: ${INSTALL_COMMAND}`);
98
99
  if (reactConfig === void 0) return [];
100
+ const jsxRuntimeConfig = readFlatConfig(reactModule, [
101
+ "configs",
102
+ "flat",
103
+ "jsx-runtime"
104
+ ]);
99
105
  const hooksModule = tryRequire("eslint-plugin-react-hooks", options.requireFn);
100
106
  return [
101
107
  reactConfig,
108
+ jsxRuntimeConfig,
102
109
  readFlatConfig(hooksModule, [
103
110
  "configs",
104
111
  "flat",
@@ -112,7 +119,7 @@ function buildReactConfig(options = {}) {
112
119
  }
113
120
  //#endregion
114
121
  //#region package.json
115
- var version = "2.10.2";
122
+ var version = "2.10.4";
116
123
  //#endregion
117
124
  //#region src/rules/barrel-helpers.ts
118
125
  const INDEX_BASENAME$1 = /^index\.[cm]?[tj]sx?$/;
@@ -144,6 +151,19 @@ function isPermittedBarrel(filename, mode) {
144
151
  if (mode === "single") return isMainBarrel(filename);
145
152
  return isIndexFile(filename);
146
153
  }
154
+ function isAncestorNode(value) {
155
+ if (typeof value !== "object" || value === null) return false;
156
+ if (!("type" in value)) return false;
157
+ return typeof value.type === "string";
158
+ }
159
+ function isInsideAmbientModuleDeclaration(node) {
160
+ let current = node;
161
+ while (isAncestorNode(current)) {
162
+ if (current.type === "TSModuleDeclaration") return true;
163
+ current = current.parent;
164
+ }
165
+ return false;
166
+ }
147
167
  function createSplitReexportDetector() {
148
168
  const importsByName = /* @__PURE__ */ new Map();
149
169
  const bareExportSpecifiers = [];
@@ -305,7 +325,7 @@ const barrelPolicy = {
305
325
  },
306
326
  ExportNamedDeclaration(node) {
307
327
  detector.visitExportNamed(node);
308
- if (hasSource(node)) {
328
+ if (hasSource(node) && !isInsideAmbientModuleDeclaration(node)) {
309
329
  const source = node.source === null || node.source === void 0 ? void 0 : node.source.value;
310
330
  if (!isPermittedBarrel(filename, mode)) context.report({
311
331
  node,
@@ -319,6 +339,7 @@ const barrelPolicy = {
319
339
  }
320
340
  },
321
341
  ExportAllDeclaration(node) {
342
+ if (isInsideAmbientModuleDeclaration(node)) return;
322
343
  const source = node.source.value;
323
344
  if (!isPermittedBarrel(filename, mode)) context.report({
324
345
  node,
package/dist/index.js CHANGED
@@ -60,16 +60,23 @@ function isFlatConfig(value) {
60
60
  }
61
61
  function buildReactConfig(options = {}) {
62
62
  if (options.enabled === false) return [];
63
- const reactConfig = readFlatConfig(tryRequire("eslint-plugin-react", options.requireFn), [
63
+ const reactModule = tryRequire("eslint-plugin-react", options.requireFn);
64
+ const reactConfig = readFlatConfig(reactModule, [
64
65
  "configs",
65
66
  "flat",
66
67
  "recommended"
67
68
  ]);
68
69
  if (options.enabled === true && reactConfig === void 0) throw new Error(`@exadev/eslint-config: React support was explicitly requested but 'eslint-plugin-react' could not be resolved. Install it with: ${INSTALL_COMMAND}`);
69
70
  if (reactConfig === void 0) return [];
71
+ const jsxRuntimeConfig = readFlatConfig(reactModule, [
72
+ "configs",
73
+ "flat",
74
+ "jsx-runtime"
75
+ ]);
70
76
  const hooksModule = tryRequire("eslint-plugin-react-hooks", options.requireFn);
71
77
  return [
72
78
  reactConfig,
79
+ jsxRuntimeConfig,
73
80
  readFlatConfig(hooksModule, [
74
81
  "configs",
75
82
  "flat",
@@ -83,7 +90,7 @@ function buildReactConfig(options = {}) {
83
90
  }
84
91
  //#endregion
85
92
  //#region package.json
86
- var version = "2.10.2";
93
+ var version = "2.10.4";
87
94
  //#endregion
88
95
  //#region src/rules/barrel-helpers.ts
89
96
  const INDEX_BASENAME$1 = /^index\.[cm]?[tj]sx?$/;
@@ -115,6 +122,19 @@ function isPermittedBarrel(filename, mode) {
115
122
  if (mode === "single") return isMainBarrel(filename);
116
123
  return isIndexFile(filename);
117
124
  }
125
+ function isAncestorNode(value) {
126
+ if (typeof value !== "object" || value === null) return false;
127
+ if (!("type" in value)) return false;
128
+ return typeof value.type === "string";
129
+ }
130
+ function isInsideAmbientModuleDeclaration(node) {
131
+ let current = node;
132
+ while (isAncestorNode(current)) {
133
+ if (current.type === "TSModuleDeclaration") return true;
134
+ current = current.parent;
135
+ }
136
+ return false;
137
+ }
118
138
  function createSplitReexportDetector() {
119
139
  const importsByName = /* @__PURE__ */ new Map();
120
140
  const bareExportSpecifiers = [];
@@ -276,7 +296,7 @@ const barrelPolicy = {
276
296
  },
277
297
  ExportNamedDeclaration(node) {
278
298
  detector.visitExportNamed(node);
279
- if (hasSource(node)) {
299
+ if (hasSource(node) && !isInsideAmbientModuleDeclaration(node)) {
280
300
  const source = node.source === null || node.source === void 0 ? void 0 : node.source.value;
281
301
  if (!isPermittedBarrel(filename, mode)) context.report({
282
302
  node,
@@ -290,6 +310,7 @@ const barrelPolicy = {
290
310
  }
291
311
  },
292
312
  ExportAllDeclaration(node) {
313
+ if (isInsideAmbientModuleDeclaration(node)) return;
293
314
  const source = node.source.value;
294
315
  if (!isPermittedBarrel(filename, mode)) context.report({
295
316
  node,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exadev/eslint-config",
3
- "version": "2.10.2",
3
+ "version": "2.10.4",
4
4
  "description": "Shared custom ESLint rules and plugin for ExaDev projects",
5
5
  "type": "module",
6
6
  "sideEffects": false,