eslint-config-beslogic 6.0.2 → 6.0.3

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/CHANGELOG.md CHANGED
@@ -1,7 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 6.0.3
4
+
5
+ - Fixed `angular-file-naming` plugin on ESLint v10 by using `fixupPluginRules`
6
+ - Pinned `eslint-plugin-no-relative-import-paths` to `^1.6` + `fixupPluginRules` rather than patched github repo
7
+ - Updated `eslint-plugin-prefer-arrow-functions` to `^3.10`
8
+ - Reverted the 6.0.2 `languageOptions.parserOptions.projectService.allowDefaultProject` change
9
+ - `eslint-rule-composer` and `eslint-plugin-react` are now patched at runtime (in-memory, when `base.mjs`/`react.mjs` are imported) instead of at install time, so `node_modules` is never written to
10
+
3
11
  ## 6.0.2
4
12
 
13
+ - ~~Added `languageOptions.parserOptions.projectService.allowDefaultProject: ["*.config.{[cm]js,js}"]` for `"files": ["*.config.{[cm][jt]s,[jt]s}"]`~~
5
14
  - Explicitly disabled the following rules in `.html` in Angular preset, as they don't parse:
6
15
  - `unicorn/prefer-string-raw`
7
16
  - `sonarjs/code-eval`
package/angular.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
  // Copyright 2026 Beslogic Inc.
3
3
 
4
4
  import { ASTUtils } from "@angular-eslint/utils"
5
+ import { fixupPluginRules } from "@eslint/compat"
5
6
  import { disableAutofix } from "@morev/eslint-disable-autofix"
6
7
  import angular from "angular-eslint"
7
8
  import { defineConfig } from "eslint/config"
@@ -122,7 +123,7 @@ export default disableAutofix(defineConfig(
122
123
  "processor": angular.processInlineTemplates,
123
124
  "plugins": {
124
125
  "rxjs-angular": rxjsAngular,
125
- "angular-file-naming": angularFileNaming
126
+ "angular-file-naming": fixupPluginRules(angularFileNaming)
126
127
  },
127
128
  "languageOptions": {
128
129
  "parserOptions": {
package/base.mjs CHANGED
@@ -11,7 +11,6 @@ import { defineConfig } from "eslint/config"
11
11
  import { builtinRules } from "eslint/use-at-your-own-risk"
12
12
  import gitignore from "eslint-config-flat-gitignore"
13
13
  import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript"
14
- import autofix from "eslint-plugin-autofix"
15
14
  import extraRules from "eslint-plugin-extra-rules"
16
15
  import { importX } from "eslint-plugin-import-x"
17
16
  import noRelativeImportPaths from "eslint-plugin-no-relative-import-paths"
@@ -24,7 +23,13 @@ import globals from "globals"
24
23
  import tseslint from "typescript-eslint"
25
24
 
26
25
  import beslogicJest from "./jest.mjs"
27
- import { cloneRule, getModuleVersion, idLengthConfig, jestFilePatterns, jestNoRestrictedSyntax } from "./lib/utils.mjs"
26
+ import { patchEslintRuleComposer } from "./lib/runtime-patch.mjs"
27
+ import { cloneRule, getModuleVersion, idLengthConfig, jestFilePatterns, jestNoRestrictedSyntax, nodeRequire } from "./lib/utils.mjs"
28
+
29
+ // Must patch before eslint-plugin-autofix (which requires eslint-rule-composer) is loaded,
30
+ // hence requiring it here instead of a static import. See lib/patch-dependencies.mjs.
31
+ patchEslintRuleComposer()
32
+ const autofix = nodeRequire("eslint-plugin-autofix")
28
33
 
29
34
  // Version deduplication across peer-dependencies makes targetting this a bit weird
30
35
  const tsEslintGlobalsVersion = Math.min(
@@ -306,7 +311,8 @@ export default disableAutofix(defineConfig(
306
311
  // TODO: consider https://github.com/jonaskello/eslint-plugin-functional/blob/HEAD/docs/rules/prefer-tacit.md
307
312
  "extra-rules": fixupPluginRules(extraRules),
308
313
  autofix,
309
- "no-relative-import-paths": noRelativeImportPaths,
314
+ // https://github.com/MelvinVermeer/eslint-plugin-no-relative-import-paths/pull/47
315
+ "no-relative-import-paths": fixupPluginRules(noRelativeImportPaths),
310
316
  "prefer-arrow-functions": preferArrowFunctions,
311
317
  "simple-import-sort": simpleImportSort
312
318
  },
@@ -1222,16 +1228,6 @@ export default disableAutofix(defineConfig(
1222
1228
  {
1223
1229
  "name": "beslogic/base/configs",
1224
1230
  "files": ["*.config.{[cm][jt]s,[jt]s}"],
1225
- "languageOptions": {
1226
- "parserOptions": {
1227
- "projectService": {
1228
- // Don't force users to include this file in their tsconfig.json,
1229
- // because it can lead to tons of type errors
1230
- // They may still have to configure this if they have nested tsconfig.json
1231
- "allowDefaultProject": ["*.config.{[cm]js,js}"]
1232
- }
1233
- }
1234
- },
1235
1231
  "rules": {
1236
1232
  // Direct anonymous exports in config files is perfectly acceptable
1237
1233
  "import-x/no-anonymous-default-export": "off",
@@ -1,7 +1,7 @@
1
1
  import { readFileSync, writeFileSync } from "node:fs"
2
- import { dirname, join } from "node:path"
2
+ import { join } from "node:path"
3
3
 
4
- import { getModuleVersion, resolveModuleLocation } from "./utils.mjs"
4
+ import { getPackageBase } from "./utils.mjs"
5
5
 
6
6
  // ANSI color chart: https://en.m.wikipedia.org/wiki/ANSI_escape_code#Colors
7
7
 
@@ -29,51 +29,6 @@ const searchAndReplace = async (
29
29
  }
30
30
  }
31
31
 
32
- const getPackageBase = (/** @type {string} */ packageName) => {
33
- const location = resolveModuleLocation(`${packageName}/package.json`)
34
- if (!location) {
35
- console.info(
36
- `\n\u001B[36m${packageName} not found in node_modules, no need to patch.\u001B[0m`
37
- )
38
-
39
- return
40
- }
41
-
42
- return dirname(location)
43
- }
44
-
45
- // Broader regexes intentional: cover any caller object (context, contextOrFilename, etc.)
46
- const eslint10 = {
47
- "getSourceCode": {
48
- "from": /(\w+)\.getSourceCode\(\)/gu,
49
- "to": "($1.sourceCode ?? $1.getSourceCode())"
50
- },
51
- "getFilename": {
52
- "from": /(\w+)\.getFilename\(\)/gu,
53
- "to": "$1.filename ?? $1.getFilename()"
54
- },
55
- "isSpaceBetweenTokens": {
56
- "from": /\.isSpaceBetweenTokens\(/gu,
57
- "to": ".isSpaceBetween("
58
- }
59
- }
60
-
61
- const shouldPatchForESLintVersion = (/** @type {string} */ packageName) => {
62
- const eslintVersion = getModuleVersion("eslint", 0)
63
- if (eslintVersion === 0) {
64
- console.info(
65
- `\nCould not find ESLint version, assuming v9, not patching ${packageName}.`
66
- )
67
- return false
68
- }
69
- if (eslintVersion < 10) {
70
- console.info(`\nESLint v${eslintVersion} detected, no need to patch ${packageName}.`)
71
- return false
72
- }
73
-
74
- return true
75
- }
76
-
77
32
  const patches = [
78
33
  async () => {
79
34
  const packageName = "@angular/material"
@@ -90,8 +45,7 @@ const patches = [
90
45
  )
91
46
 
92
47
  return packageName
93
- },
94
-
48
+ }
95
49
  // TODO: Fix nx patching
96
50
  // async () => {
97
51
  // const packageName = "@nx/angular"
@@ -183,63 +137,7 @@ const patches = [
183
137
  // )
184
138
 
185
139
  // return packageName
186
- // },
187
- // ESLint 10 removed context.getFilename()/getSourceCode();
188
- // eslint-rule-composer (unmaintained) still calls them.
189
- // Patch with ?? fallback.
190
- async () => {
191
- const packageName = "eslint-rule-composer"
192
- if (!shouldPatchForESLintVersion(packageName)) return
193
- const base = getPackageBase(packageName)
194
- if (!base) return
195
- await searchAndReplace(
196
- join(base, "lib", "rule-composer.js"),
197
- [
198
- eslint10.getFilename,
199
- eslint10.getSourceCode
200
- ]
201
- )
202
-
203
- return packageName
204
- },
205
- // ESLint 10 removed context.getFilename()/getSourceCode()/isSpaceBetweenTokens();
206
- // eslint-plugin-react 7.x still calls them. Patch with ?? fallback / renamed methods.
207
- async () => {
208
- const packageName = "eslint-plugin-react"
209
- if (!shouldPatchForESLintVersion(packageName)) return
210
- const base = getPackageBase(packageName)
211
- if (!base) return
212
- await Promise.all([
213
- searchAndReplace(
214
- join(base, "lib", "util", "version.js"),
215
- [eslint10.getFilename]
216
- ),
217
- searchAndReplace(
218
- join(base, "lib", "rules", "jsx-filename-extension.js"),
219
- [eslint10.getFilename]
220
- ),
221
- searchAndReplace(
222
- join(base, "lib", "rules", "forward-ref-uses-ref.js"),
223
- [eslint10.getSourceCode]
224
- ),
225
- ...[
226
- "jsx-curly-spacing.js",
227
- "jsx-one-expression-per-line.js",
228
- "jsx-tag-spacing.js",
229
- "jsx-equals-spacing.js",
230
- "jsx-space-before-closing.js"
231
- ]
232
- .map(file =>
233
- searchAndReplace(
234
- join(base, "lib", "rules", file),
235
- [eslint10.isSpaceBetweenTokens]
236
- )
237
- // eslint-disable-next-line @stylistic/function-paren-newline -- See note this can't be fully consistent with dprint
238
- )
239
- ])
240
-
241
- return packageName
242
- }
140
+ // }
243
141
  ].map(
244
142
  patch =>
245
143
  patch().then(
@@ -0,0 +1,146 @@
1
+ import { readFileSync } from "node:fs"
2
+ import Module from "node:module"
3
+ import { join } from "node:path"
4
+
5
+ import { getModuleVersion, getPackageBase } from "./utils.mjs"
6
+
7
+ // Broader regexes intentional: cover any caller object (context, contextOrFilename, etc.)
8
+ const eslint10 = {
9
+ "getSourceCode": {
10
+ "from": /(\w+)\.getSourceCode\(\)/gu,
11
+ "to": "($1.sourceCode ?? $1.getSourceCode())"
12
+ },
13
+ "getFilename": {
14
+ "from": /(\w+)\.getFilename\(\)/gu,
15
+ "to": "$1.filename ?? $1.getFilename()"
16
+ },
17
+ "isSpaceBetweenTokens": {
18
+ "from": /\.isSpaceBetweenTokens\(/gu,
19
+ "to": ".isSpaceBetween("
20
+ }
21
+ }
22
+
23
+ const shouldPatchForESLintVersion = (/** @type {string} */ packageName) => {
24
+ const eslintVersion = getModuleVersion("eslint", 0)
25
+ if (eslintVersion === 0 && packageName) {
26
+ console.info(
27
+ `\n[36mCould not find ESLint version, assuming v9, not patching ${packageName}.[0m`
28
+ )
29
+ return false
30
+ }
31
+ // eslint-disable-next-line sonarjs/prefer-single-boolean-return -- Could add log back
32
+ if (eslintVersion < 10) {
33
+ // console.info(`\n[36mESLint v${eslintVersion} detected, no need to patch ${packageName}.[0m`)
34
+ return false
35
+ }
36
+
37
+ return true
38
+ }
39
+
40
+ /*
41
+ * Runtime patching: instead of rewriting files inside node_modules on disk (which doesn't play
42
+ * well with content-addressable stores like pnpm's, read-only node_modules, or CI caches),
43
+ * we intercept Node's CommonJS loader and rewrite the source of specific files in-memory,
44
+ * the moment they get `require`d. Callers must trigger the patch (and only then dynamically
45
+ * `import()` the package that needs it) so the hook is installed before the target file is
46
+ * first loaded - see base.mjs and react.mjs.
47
+ */
48
+ // _extensions/_cache are undocumented CommonJS loader internals, absent from @types/node.
49
+ const ModuleInternals = /** @type {any} */ (Module)
50
+
51
+ /** @type {Map<string, {from: string | RegExp, to: string}[]>} */
52
+ const patchedSources = new Map()
53
+ let isRuntimePatchHookInstalled = false
54
+
55
+ const installRuntimePatchHook = () => {
56
+ if (isRuntimePatchHookInstalled) return
57
+ isRuntimePatchHookInstalled = true
58
+
59
+ const originalJsLoader = ModuleInternals._extensions[".js"]
60
+ ModuleInternals._extensions[".js"] = (
61
+ /** @type {{ _compile: (source: string, filename: string) => unknown }} */ module,
62
+ /** @type {string} */ filename
63
+ ) => {
64
+ const replacements = patchedSources.get(filename)
65
+ if (!replacements) {
66
+ originalJsLoader(module, filename)
67
+ return
68
+ }
69
+
70
+ try {
71
+ // eslint-disable-next-line n/no-sync -- CommonJS module loading is inherently synchronous
72
+ let source = readFileSync(filename, "utf8")
73
+ for (const replacement of replacements) {
74
+ source = source.replaceAll(replacement.from, replacement.to)
75
+ }
76
+ module._compile(source, filename)
77
+ } catch (error) {
78
+ console.error(
79
+ "\n\u001B[31m"
80
+ // eslint-disable-next-line @stylistic/indent-binary-ops -- dprint incompatibility
81
+ + `eslint-config-beslogic failed to patch ${filename} at runtime, falling back to the original file. `
82
+ + "Please contact samuel.therrien@beslogic.com with the package version and the full error to get it fixed."
83
+ + "\u001B[0m"
84
+ )
85
+ console.error(error)
86
+ originalJsLoader(module, filename)
87
+ }
88
+ }
89
+ }
90
+
91
+ const patchAtRuntime = (
92
+ /** @type {string} */ absoluteFilePath,
93
+ /** @type {{from: string | RegExp, to: string}[]} */ replacements
94
+ ) => {
95
+ installRuntimePatchHook()
96
+ patchedSources.set(absoluteFilePath, replacements)
97
+ // In the unlikely case something already required this file in this process,
98
+ // evict it so the next require() picks up the patched source.
99
+ Reflect.deleteProperty(ModuleInternals._cache, absoluteFilePath)
100
+ }
101
+
102
+ // ESLint 10 removed context.getFilename()/getSourceCode();
103
+ // eslint-rule-composer (unmaintained) still calls them.
104
+ // Patch with ?? fallback.
105
+ export const patchEslintRuleComposer = () => {
106
+ const packageName = "eslint-rule-composer"
107
+ if (!shouldPatchForESLintVersion(packageName)) return
108
+ const base = getPackageBase(packageName)
109
+ if (!base) return
110
+
111
+ patchAtRuntime(
112
+ join(base, "lib", "rule-composer.js"),
113
+ [
114
+ eslint10.getFilename,
115
+ eslint10.getSourceCode
116
+ ]
117
+ )
118
+
119
+ console.info(`\u001B[32m${packageName} patched at runtime to support ESLint v10!\u001B[0m`)
120
+ }
121
+
122
+ // ESLint 10 removed context.getFilename()/getSourceCode()/isSpaceBetweenTokens();
123
+ // eslint-plugin-react 7.x still calls them. Patch with ?? fallback / renamed methods.
124
+ export const patchEslintPluginReact = () => {
125
+ const packageName = "eslint-plugin-react"
126
+ if (!shouldPatchForESLintVersion(packageName)) return
127
+ const base = getPackageBase(packageName)
128
+ if (!base) return
129
+
130
+ patchAtRuntime(join(base, "lib", "util", "version.js"), [eslint10.getFilename])
131
+ patchAtRuntime(join(base, "lib", "rules", "jsx-filename-extension.js"), [eslint10.getFilename])
132
+ patchAtRuntime(join(base, "lib", "rules", "forward-ref-uses-ref.js"), [eslint10.getSourceCode])
133
+ for (
134
+ const file of [
135
+ "jsx-curly-spacing.js",
136
+ "jsx-one-expression-per-line.js",
137
+ "jsx-tag-spacing.js",
138
+ "jsx-equals-spacing.js",
139
+ "jsx-space-before-closing.js"
140
+ ]
141
+ ) {
142
+ patchAtRuntime(join(base, "lib", "rules", file), [eslint10.isSpaceBetweenTokens])
143
+ }
144
+
145
+ console.info(`\u001B[32m${packageName} patched at runtime to support ESLint v10!\u001B[0m`)
146
+ }
package/lib/utils.mjs CHANGED
@@ -1,7 +1,12 @@
1
1
  import { createRequire } from "node:module"
2
+ import { dirname } from "node:path"
2
3
 
3
4
  const require = createRequire(import.meta.url)
4
5
 
6
+ // Exposed so callers can synchronously `require()` a CommonJS package right after triggering
7
+ // a runtime patch for it (see lib/patch-dependencies.mjs), without introducing top-level await.
8
+ export const nodeRequire = require
9
+
5
10
  export const getModuleVersion = (
6
11
  /** @type {string} */ nodeModule,
7
12
  /** @type {number} */ fallbackVersion
@@ -30,6 +35,19 @@ export const resolveModuleLocation = (/** @type {string} */ nodeModule) => {
30
35
  }
31
36
  }
32
37
 
38
+ export const getPackageBase = (/** @type {string} */ packageName) => {
39
+ const location = resolveModuleLocation(`${packageName}/package.json`)
40
+ if (!location) {
41
+ console.info(
42
+ `\n\u001B[36m${packageName} not found in node_modules, no need to patch.\u001B[0m`
43
+ )
44
+
45
+ return
46
+ }
47
+
48
+ return dirname(location)
49
+ }
50
+
33
51
  export const jestNoRestrictedSyntax = (
34
52
  /** @type {readonly (string | {readonly selector: string})[]} */ noRestrictedSyntax
35
53
  ) => /** @type {import('@eslint/core').RuleConfig} */ ([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-config-beslogic",
3
- "version": "6.0.2",
3
+ "version": "6.0.3",
4
4
  "description": "ESLint rules, plugins and configs used at Beslogic",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -46,7 +46,6 @@
46
46
  },
47
47
  "// dependencies": "Not using peerDependencies because I want Yarn to automatically install them without the end user having to specify everything",
48
48
  "// globals": "See this issue that prevented linting https://github.com/sindresorhus/globals/releases/tag/v13.12.1",
49
- "// eslint-plugin-no-relative-import-paths": "https://github.com/MelvinVermeer/eslint-plugin-no-relative-import-paths/pull/47",
50
49
  "// eslint-plugin-unicorn": "v65 supports eslint@10, v66 forces it. Keeping it at 65 for now makes package resolution easier",
51
50
  "// eslint-plugin-unicorn (cont)": "AND I can deal with learning all the new rules another time.",
52
51
  "dependencies": {
@@ -62,8 +61,8 @@
62
61
  "eslint-plugin-extra-rules": "^0.0.0-development",
63
62
  "eslint-plugin-import-x": "^4.15.1",
64
63
  "eslint-plugin-jest": "^28.8.3 || ^29.0.1",
65
- "eslint-plugin-no-relative-import-paths": "github:MelvinVermeer/eslint-plugin-no-relative-import-paths#908b2d101e43a0268bcb557f2d6a3be2ccc5bb71",
66
- "eslint-plugin-prefer-arrow-functions": "github:SamuelT-Beslogic/eslint-plugin-prefer-arrow-functions#1cf8ac41159095e088380faf1f103e8a1bad9b7b",
64
+ "eslint-plugin-no-relative-import-paths": "^1.6",
65
+ "eslint-plugin-prefer-arrow-functions": "^3.10",
67
66
  "eslint-plugin-regexp": "^2.3",
68
67
  "eslint-plugin-simple-import-sort": ">=12.0",
69
68
  "eslint-plugin-sonarjs": "^3.0.2 || ^4.0.3",
package/react.mjs CHANGED
@@ -4,14 +4,19 @@
4
4
  import { disableAutofix } from "@morev/eslint-disable-autofix"
5
5
  import { defineConfig } from "eslint/config"
6
6
  import jsxA11y from "eslint-plugin-jsx-a11y"
7
- import react from "eslint-plugin-react"
8
7
  import reactHooks from "eslint-plugin-react-hooks"
9
8
  import preferFunctionComponent from "eslint-plugin-react-prefer-function-component/config"
10
9
  import testingLibrary from "eslint-plugin-testing-library"
11
10
  import globals from "globals"
12
11
 
13
12
  import beslogicBase, { autofixPreferArrowFunctionsConfig, noAutofixPreferArrowFunctionsConfig, PREVENT_ABBREVIATION_KEY, preventAbbreviationsConfig as preventAbbreviationsConfigJS } from "./base.mjs"
14
- import { jestFilePatterns, jsxSortPropsConfig } from "./lib/utils.mjs"
13
+ import { patchEslintPluginReact } from "./lib/runtime-patch.mjs"
14
+ import { jestFilePatterns, jsxSortPropsConfig, nodeRequire } from "./lib/utils.mjs"
15
+
16
+ // Must patch before eslint-plugin-react is loaded, hence requiring it here instead of a
17
+ // static import. See lib/patch-dependencies.mjs.
18
+ patchEslintPluginReact()
19
+ const react = nodeRequire("eslint-plugin-react")
15
20
 
16
21
  const typeDeclarationOverride = beslogicBase.find(
17
22
  config => config.name === "beslogic/base/type-declaration"
package/stylistic.mjs CHANGED
@@ -3,10 +3,20 @@
3
3
 
4
4
  import stylistic from "@stylistic/eslint-plugin"
5
5
  import { defineConfig } from "eslint/config"
6
- import autofix from "eslint-plugin-autofix"
7
- import react from "eslint-plugin-react"
8
6
  import eslintPluginUnicorn from "eslint-plugin-unicorn"
9
7
 
8
+ import { patchEslintPluginReact, patchEslintRuleComposer } from "./lib/runtime-patch.mjs"
9
+ import { nodeRequire } from "./lib/utils.mjs"
10
+
11
+ // Must patch before eslint-plugin-autofix (which requires eslint-rule-composer) is loaded,
12
+ // hence requiring it here instead of a static import. See lib/patch-dependencies.mjs.
13
+ patchEslintRuleComposer()
14
+ const autofix = nodeRequire("eslint-plugin-autofix")
15
+ // Must patch before eslint-plugin-react is loaded, hence requiring it here instead of a
16
+ // static import. See lib/patch-dependencies.mjs.
17
+ patchEslintPluginReact()
18
+ const react = nodeRequire("eslint-plugin-react")
19
+
10
20
  const commaDangleDefault = "always-multiline"
11
21
 
12
22
  export default defineConfig(