Eslint: FlatCompat utility and its work and magic, a deep dive that demystifies Flat config and…
A very deep dive into FlatCompat utility. That does teach us a lot. From how it works. To a lot about how Flat config works. And how we can…
Eslint: FlatCompat utility and its work and magic, a deep dive that demystifies Flat config and translation from eslintrc
A very deep dive into FlatCompat utility. That does teach us a lot. From how it works. To a lot about how Flat config works. And how we can translate from eslintrc to Flat config

Resume and Navigation
- Check
Ok cool what are the lessonssection all at the end (CTRL|CMD + F) [The whole resume go there] ◌ If you are in a hurry, you can skim through the whole, or directly pass to that resume section - Also, you can follow in
GitHubwith the code source - You will get to see how
FlatCompatworks Normalization patternwith a great open-source example- How to transform
Eslintrc configtoFlatConfig - Eslint
modulesNameresolution algorithm - A demonstration of how you use the
debuggerinvscodetoanalyzeandunderstandthecodeand it’sexecution - Event loop in action and in relation to debugger stepping
- generator pattern, explanation, and insight
- Code consistency and its value in debugging, searching, and understanding the code base fast
FlatCompat tool repo
- https://github.com/eslint/eslintrc#readme
- https://github.com/eslint/eslintrc/blob/af564ff562bd4f8c40163b8a44ed9d4989adff6a/lib/flat-compat.js#L41 🔥
FlatCompat full usage example
FlatCompact how it works and it’s magic
✨ First, all calls to other than config() will call config()
(config())

/**
* Translates the `env` section of an ESLintRC-style config.
* @param {Object} envConfig The `env` section of an ESLintRC config.
* @returns {Object[]} An array of flag-config objects representing the environments.
*/
env(envConfig) {
return this.config({
env: envConfig
});
}
/**
* Translates the `extends` section of an ESLintRC-style config.
* @param {...string} configsToExtend The names of the configs to load.
* @returns {Object[]} An array of flag-config objects representing the config.
*/
extends(...configsToExtend) {
return this.config({
extends: configsToExtend
});
}
/**
* Translates the `plugins` section of an ESLintRC-style config.
* @param {...string} plugins The names of the plugins to load.
* @returns {Object[]} An array of flag-config objects representing the plugins.
*/
plugins(...plugins) {
return this.config({
plugins
});
}
- So we will focus on
config()function
✨ How does config() works
(config())

config(eslintrcConfig) {
const eslintrcArray = this[cafactory].create(eslintrcConfig, {
basePath: this.baseDirectory
});
const flatArray = [];
let hasIgnorePatterns = false;
eslintrcArray.forEach(configData => {
if (configData.type === "config") {
hasIgnorePatterns = hasIgnorePatterns || configData.ignorePattern;
flatArray.push(...translateESLintRC(configData, {
resolveConfigRelativeTo: path__default["default"].join(this.baseDirectory, "__placeholder.js"),
resolvePluginsRelativeTo: path__default["default"].join(this.resolvePluginsRelativeTo, "__placeholder.js"),
pluginEnvironments: eslintrcArray.pluginEnvironments,
pluginProcessors: eslintrcArray.pluginProcessors
}));
}
});
// combine ignorePatterns to emulate ESLintRC behavior better
if (hasIgnorePatterns) {
flatArray.unshift({
ignores: [filePath => {
// Compute the final config for this file.
// This filters config array elements by `files`/`excludedFiles` then merges the elements.
const finalConfig = eslintrcArray.extractConfig(filePath);
// Test the `ignorePattern` properties of the final config.
return Boolean(finalConfig.ignores) && finalConfig.ignores(filePath);
}]
});
}
return flatArray;
}
- get our passed old
eslintconfig ◌ And it would make magic totranslateit toflat config
✨ Before starting, things to establish
▪️ config
- take one
plugin configat a time - And
translatesomethingequivforflat config◌ that would rely on the nature of overriding or deep merging offlat config
The way we use compat.config()
is
[
...compat.extends('plugin:prettier/recommended'),
]
- it does return a
flat config(array)
✨ Normalizing eslintrc config
(normalizing, eslintrc)
- Factory
const eslintrcArray = this[cafactory].create(eslintrcConfig, {
basePath: this.baseDirectory
});

// Create `ConfigArray` instance from a config data.
create(configData, { basePath, filePath, name } = {}) {
if (!configData) {
return new ConfigArray();
}
const slots = internalSlotsMap$1.get(this);
const ctx = createContext(slots, "config", name, filePath, basePath);
const elements = this._normalizeConfigData(configData, ctx);
return new ConfigArray(...elements);
}
▪️ A normalization goes first before transformation
normalizationwhen it comes toprocessingandparsingconfig◌ is a common practice ◌ You create a **one-config format- **prepared specifically for your processing requirement
- And it’s concise and specific
- Not normalized (many possibilities, different formats …) => normalized (on format) ▶︎ Can code with it simply. It simplifies. Simple direct reading. And ease processing.
Ok, we can just stop at this.
Or let’s get curious

▪️ opening the js debugging console
- A very powerful beauty gem of
vscode◌ available for years now
Let’s add breakpoints


Let's execute eslint . on the debugging console


debuggerattached and pause at ourbreakpoint- using the
debuggerlike this, is the fastest way to understand the execution and the code - as well as the structure of variables ◌ especially if a lot doesn’t matter to you
Either step in or step over and you keep going step by step

Or in our case, we want to go directly to create
- So I'll hit
continue

My breakpoint wasn’t hit

Try again
I used step inin this time

step inagain

- I can add
breakpointsnow ◌ You can always addbreakpointswhile things are running
But it's fine with step in

Step over to advance

step over

- you can see, I can see the structure of the
slots

And we can now, see things in action and how they evolve
at this step, we can go step in and discover the createContext() or i can skip

stepped inwe could have skipped
Step over multiple times
I can check the variables as well as I'm seeing the code

- the context format for now is
return { filePath, matchBasePath, name, pluginBasePath, type };
values

We could have skipped

And we get the context after
I stepped in again

- normalization
◌ we can see the
validationpart ofnormalization◌ And it’s acriticalandcommonstep innormalization


- we can see the config passed to it

- we can see the
validate schema

- And basically, it’s
eslintrc configschema - validation pass with no issue


- checking for
ecmaFeatures - in our case there is
none◌ Which is a deprecated feature and only for warning

- Validation of schema done
We can move on

getting me back ❗
because i over stepover the function (mistake) ❗

- Elements here is a generator

✨ Use of ignores at the start of the flat config

- For time purposes, I will not keep
stepping in

- criteria
null

override testerseems totestforoverride criteria◌ We can imagine what the component does
We see overrides props are handled through the OverrideTester

And that this tool does help us with such config
At this step because elements is a generator, let’s go with step in
And see how the generator is working
And what’s doing

This function handles the different config properties

In our case we have extends only
and it would be handled in that block


_loadExtends()

- Search in
libfolder

▪️ We see all similar functions
- for
loadfunctions - for the different properties
- In a project with good convention and good consistency
◌ Searching that way is too efficient and would show you all similar things
◌ that’s the
powerofgood editors
// Flatten `extends`.
for (const extendName of extendList.filter(Boolean)) {
yield* this._loadExtends(extendName, ctx);
}
▪️ let’s explain the yield (Skip or skim, if you are already familiar with generators)
- we are inside a
generator◌function, withsyntax-ic sugarforiterators◌ just likeasync await◌Generators(if not familiar), are popular across all languages ◌ Andyieldis the equiv ofawaitorreturn yieldreturn the value and pause the execution. Untilgenerator.next()is called again. Where the execution will resume from that lastyieldpause place in the code and that’s why it’s a syntaxic sugar likeawait. Thegeneratorsyntaxallows the creation of aniteratoreasily (just likeawaitdoes withpromises).- A
Generatoris afunction. In whichyieldis used. Thatreturnaniterator. Theiteratorwithnext()API is used toiteratethrough the execution context (defined by theyieldpoints). Eachnext()call will start theexecutionfrom the lastyieldpoint to the next one and pause. Till the nextnext()call will be made. Ifreturnis encountered. Theiteratorcomes to an end. - In the
loopaboveyieldwill return the value ofloadExtends()and pause the execution. Untilgenerator.next()is called. Where the execution will continue from that sameyieldpoint. - gen.next() => exec till encounter
yield=>pause,returnval — once we callgen.next()again => resume execution fromlastyield(pause point) => run until the encounter of anotheryield=>pause,returnval => …

for ofsupportiteratorsfor ofis working by each time calling ◌elements.next()🔥 ◌ when that happens => our function will execute 🔥 ◌ until it reaches ayield🔥- ◌ at that point as with
awaitorreturn◌ The value ofnext()is returned ◌ in this case it will be stored inelementvariable ◌ our loop block will execute ◌ Then againofwill go 🔥 ◌ and.next()called again 🔥 ◌ however this time ▶︎ the execution of the function will start just after the line containingyield🔥 ▶︎generatorandyieldallow abreakpointforstopping▶ ︎and later it would resume from there ▶︎ check out a course aboutgeneratorsor [my article about it][Will be added later, TK] - check how to translate
generatorstoiterators - ✨ The best way also, to play with that is to go to babel playground 🔥
- and use a
generator, for example, the function above - And check the
outputiniteratorformat - basically, what the
syntax-ic sugartranslate to




You can see the iterator
and the usage of switch
and next context to hold which block to run next ….
- Function in
generatorformat
function* _normalizeObjectConfigDataBody(
{
env,
extends: extend,
globals,
ignorePatterns,
noInlineConfig,
parser: parserName,
parserOptions,
plugins: pluginList,
processor,
reportUnusedDisableDirectives,
root,
rules,
settings,
overrides: overrideList = []
},
ctx
) {
const extendList = Array.isArray(extend) ? extend : [extend];
const ignorePattern = ignorePatterns && new IgnorePattern(
Array.isArray(ignorePatterns) ? ignorePatterns : [ignorePatterns],
ctx.matchBasePath
);
// Flatten `extends`.
for (const extendName of extendList.filter(Boolean)) {
yield* this._loadExtends(extendName, ctx);
}
// Load parser & plugins.
const parser = parserName && this._loadParser(parserName, ctx);
const plugins = pluginList && this._loadPlugins(pluginList, ctx);
// Yield pseudo config data for file extension processors.
if (plugins) {
yield* this._takeFileExtensionProcessors(plugins, ctx);
}
// Yield the config data except `extends` and `overrides`.
yield {
// Debug information.
type: ctx.type,
name: ctx.name,
filePath: ctx.filePath,
// Config data.
criteria: null,
env,
globals,
ignorePattern,
noInlineConfig,
parser,
parserOptions,
plugins,
processor,
reportUnusedDisableDirectives,
root,
rules,
settings
};
// Flatten `overries`.
for (let i = 0; i < overrideList.length; ++i) {
yield* this._normalizeObjectConfigData(
overrideList[i],
{ ...ctx, name: `${ctx.name}#overrides[${i}]` }
);
}
}
- Function in iterator format by
BabelPlaygroundtranspilationcompiledfornodejsES5
jafunction _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == typeof h && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw new Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator.return && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(typeof e + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw new Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, catch: function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw new Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
function _normalizeObjectConfigDataBody(_ref, ctx) {
var _this = this;
var env = _ref.env,
extend = _ref.extends,
globals = _ref.globals,
ignorePatterns = _ref.ignorePatterns,
noInlineConfig = _ref.noInlineConfig,
parserName = _ref.parser,
parserOptions = _ref.parserOptions,
pluginList = _ref.plugins,
processor = _ref.processor,
reportUnusedDisableDirectives = _ref.reportUnusedDisableDirectives,
root = _ref.root,
rules = _ref.rules,
settings = _ref.settings,
_ref$overrides = _ref.overrides,
overrideList = _ref$overrides === void 0 ? [] : _ref$overrides;
return /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
var extendList, ignorePattern, _iterator, _step, extendName, parser, plugins, i;
return _regeneratorRuntime().wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
extendList = Array.isArray(extend) ? extend : [extend];
ignorePattern = ignorePatterns && new IgnorePattern(Array.isArray(ignorePatterns) ? ignorePatterns : [ignorePatterns], ctx.matchBasePath); // Flatten `extends`.
_iterator = _createForOfIteratorHelperLoose(extendList.filter(Boolean));
case 3:
if ((_step = _iterator()).done) {
_context.next = 8;
break;
}
extendName = _step.value;
return _context.delegateYield(_this._loadExtends(extendName, ctx), "t0", 6);
case 6:
_context.next = 3;
break;
case 8:
// Load parser & plugins.
parser = parserName && _this._loadParser(parserName, ctx);
plugins = pluginList && _this._loadPlugins(pluginList, ctx); // Yield pseudo config data for file extension processors.
if (!plugins) {
_context.next = 12;
break;
}
return _context.delegateYield(_this._takeFileExtensionProcessors(plugins, ctx), "t1", 12);
case 12:
_context.next = 14;
return {
// Debug information.
type: ctx.type,
name: ctx.name,
filePath: ctx.filePath,
// Config data.
criteria: null,
env,
globals,
ignorePattern,
noInlineConfig,
parser,
parserOptions,
plugins,
processor,
reportUnusedDisableDirectives,
root,
rules,
settings
};
case 14:
i = 0;
case 15:
if (!(i < overrideList.length)) {
_context.next = 20;
break;
}
return _context.delegateYield(_this._normalizeObjectConfigData(overrideList[i], _objectSpread(_objectSpread({}, ctx), {}, {
name: `${ctx.name}#overrides[${i}]`
})), "t2", 17);
case 17:
++i;
_context.next = 15;
break;
case 20:
case "end":
return _context.stop();
}
}, _callee);
})();
}
Ok [TODO] REMOVE THAT PART and set it for its own article
- leave links only

- let’s
step inlet’sstep in

_loadExtends(extendName, ctx) {
debug("Loading {extends:%j} relative to %s", extendName, ctx.filePath);
try {
if (extendName.startsWith("eslint:")) {
return this._loadExtendedBuiltInConfig(extendName, ctx);
}
if (extendName.startsWith("plugin:")) {
return this._loadExtendedPluginConfig(extendName, ctx);
}
return this._loadExtendedShareableConfig(extendName, ctx);
} catch (error) {
error.message += `\nReferenced from: ${ctx.filePath || ctx.name}`;
throw error;
}
}
- from just that
blockwe get to know howextendsworks andeslintconvention when it comes to it
▪️ eslint:
- by
eslintextends
▪ ️plugin:
- by plugin extends (
eslint-plugin-prettier) ◌eslint-plugin-
▪️ none
- A sharable config
eslint-config-prettier◌ not plugin-related ◌eslint-config-

our start with plugin

let’s step in

_loadExtendedPluginConfig(extendName, ctx) {
const slashIndex = extendName.lastIndexOf("/");
if (slashIndex === -1) {
throw configInvalidError(extendName, ctx.filePath, "plugin-invalid");
}
const pluginName = extendName.slice("plugin:".length, slashIndex);
const configName = extendName.slice(slashIndex + 1);
if (isFilePath(pluginName)) {
throw new Error("'extends' cannot use a file path for plugins.");
}
const plugin = this._loadPlugin(pluginName, ctx);
const configData =
plugin.definition &&
plugin.definition.configs[configName];
if (configData) {
return this._normalizeConfigData(configData, {
...ctx,
filePath: plugin.filePath || ctx.filePath,
name: `${ctx.name} » plugin:${plugin.id}/${configName}`
});
}
throw plugin.error || configInvalidError(extendName, ctx.filePath, "extend-config-missing");
}

- you get why
/and what is it ◌ it’s a separation to precise which config to pickup ◌ if multiple are provided




loadPlugin() function
_loadPlugin(name, ctx) {
debug("Loading plugin %j from %s", name, ctx.filePath);
const { additionalPluginPool, resolver } = internalSlotsMap.get(this);
const request = naming.normalizePackageName(name, "eslint-plugin");
const id = naming.getShorthandName(request, "eslint-plugin");
const relativeTo = path.join(ctx.pluginBasePath, "__placeholder__.js");
if (name.match(/\s+/u)) {
const error = Object.assign(
new Error(`Whitespace found in plugin name '${name}'`),
{
messageTemplate: "whitespace-found",
messageData: { pluginName: request }
}
);
return new ConfigDependency({
error,
id,
importerName: ctx.name,
importerPath: ctx.filePath
});
}
// Check for additional pool.
const plugin =
additionalPluginPool.get(request) ||
additionalPluginPool.get(id);
if (plugin) {
return new ConfigDependency({
definition: normalizePlugin(plugin),
filePath: "", // It's unknown where the plugin came from.
id,
importerName: ctx.name,
importerPath: ctx.filePath
});
}
let filePath;
let error;
try {
filePath = resolver.resolve(request, relativeTo);
} catch (resolveError) {
error = resolveError;
/* istanbul ignore else */
if (error && error.code === "MODULE_NOT_FOUND") {
error.messageTemplate = "plugin-missing";
error.messageData = {
pluginName: request,
resolvePluginsRelativeTo: ctx.pluginBasePath,
importerName: ctx.name
};
}
}
if (filePath) {
try {
writeDebugLogForLoading(request, relativeTo, filePath);
const startTime = Date.now();
const pluginDefinition = require(filePath);
debug(`Plugin ${filePath} loaded in: ${Date.now() - startTime}ms`);
return new ConfigDependency({
definition: normalizePlugin(pluginDefinition),
filePath,
id,
importerName: ctx.name,
importerPath: ctx.filePath
});
} catch (loadError) {
error = loadError;
}
}
debug("Failed to load plugin '%s' declared in '%s'.", name, ctx.name);
error.message = `Failed to load plugin '${name}' declared in '${ctx.name}': ${error.message}`;
return new ConfigDependency({
error,
id,
importerName: ctx.name,
importerPath: ctx.filePath
});
}

it's apackage resolution function

- does some validations
- and handle dependency

eslint-plugin-prettier have none


resolver.resolve()resolve the plugin file path

- require


- loaded correctly
- now the
FlatCompattool will be able to work with it - The tool is doing the loading for us
- As all was happening in the past
Another normalizing function
normalizePlugin(pluginDefinition)

- to normalize plugin definition

Ok we can't help but be curious
function normalizePlugin(plugin) {
// first check the cache
let normalizedPlugin = normalizedPlugins.get(plugin);
if (normalizedPlugin) {
return normalizedPlugin;
}
normalizedPlugin = {
configs: plugin.configs || {},
environments: plugin.environments || {},
processors: plugin.processors || {},
rules: plugin.rules || {}
};
// save the reference for later
normalizedPlugins.set(plugin, normalizedPlugin);
return normalizedPlugin;
}
- that’s the normalization logic

pluginloaded, ready to be used


- All this, to get the
pluginconfigdata - Then we have to
normalizeit - When you
normalizedata, you can match and count on it ◌Datanormalizationease up working withdata. By creatingstructure. And oneformat.

- You know the
function - And we are using
recursivity - And you can know why?
◌ In case of
modulesthat would haveextendsand otherpropsas well M1 (extend (M2 extend (M3 …)))◌ So all needs to happenrecursively
I'm passing



recursivitykicking in

this time extends: ["prettier"]
- => will be resolved to
eslint-config-prettierwhich is a fully different package

- we are going again ◌ same trip

again, an extends

a sharable config

resolutiontopackagename done- let's get the
packagepath

Here it goes

'/Users/mohamedlamineallal/repos/dotfiles-wizard/node_modules/.pnpm/eslint-config-prettier@9.0.0_eslint@8.48.0/node_modules/eslint-config-prettier/index.js'

Same thing but this time the config will have rules only
pluginwill berequired- and the whole
normalizationwill be applied as well - we move on

Again the same normalization function (generator)

recursivity
- This time the
objecthasrulesonly - So no more
iterationsafter that


this time we will stop

there is none
So we are moving to the next logic

You can see how generators are beautiful
recursion is better
managing flow is better
Condition with flow works well
…..

- no
parserskip - neither
plugin

Hello we are going to yield an actual data without recursion, finally

after yield , the generator continue
- I did a
step over - should have done a
step in



done with the function return
Generator for of will stop as well



You can see that the data is being normalized
And set in their correct section
- now after
normalizationfinish Transformationwill go

▪️ plugins here are the actual loaded plugins
- nodejs
require◌ ++



▪️ plugins, rules are correctly loaded
pluginsloaded with actualobjetsnotstring◌ exactly whatflat configrequire

- same thing as before

Done normalizing

recursivityupward ◌ I skipped astep

Hola back


We could have skipped all that journey
- I did it only to showcase
debuggingusage - And for some
curiosity◌ checking the code

▪ ️Two configs are loaded fully
eslint-plugin-prettierplugin ◌ withpluginloaded correctlyeslint-config-prettierrules config- The third is empty
▪️ resolution and normalization done at this stage 🔥
- We can start with the
transformation
✨ Transformation 🔥🔥

- missed to use
step in(step overinstead) it was a forEach()
We can see the transformation applied
For each eslintrc element
a block config in flat config is generated
- we can see the one of
eslint-config-prettier◌ onlyrules - and the one of
eslint-plugin-prettier◌rules◌plugins
We can understand well how things are working now
I'll go again and get to step on translateEslintRc()
Or wait no need. Here is the whole function the logic is clear
- We can see the execution. Or directly read the code. Let’s break it down.
/**
* Translates an ESLintRC-style config object into a flag-config-style config
* object.
* @param {Object} eslintrcConfig An ESLintRC-style config object.
* @param {Object} options Options to help translate the config.
* @param {string} options.resolveConfigRelativeTo To the directory to resolve
* configs from.
* @param {string} options.resolvePluginsRelativeTo The directory to resolve
* plugins from.
* @param {ReadOnlyMap<string,Environment>} options.pluginEnvironments A map of plugin environment
* names to objects.
* @param {ReadOnlyMap<string,Processor>} options.pluginProcessors A map of plugin processor
* names to objects.
* @returns {Object} A flag-config-style config object.
*/
function translateESLintRC(eslintrcConfig, {
resolveConfigRelativeTo,
resolvePluginsRelativeTo,
pluginEnvironments,
pluginProcessors
}) {
const flatConfig = {};
const configs = [];
const languageOptions = {};
const linterOptions = {};
const keysToCopy = ["settings", "rules", "processor"];
const languageOptionsKeysToCopy = ["globals", "parser", "parserOptions"];
const linterOptionsKeysToCopy = ["noInlineConfig", "reportUnusedDisableDirectives"];
// copy over simple translations
for (const key of keysToCopy) {
if (key in eslintrcConfig && typeof eslintrcConfig[key] !== "undefined") {
flatConfig[key] = eslintrcConfig[key];
}
}
// copy over languageOptions
for (const key of languageOptionsKeysToCopy) {
if (key in eslintrcConfig && typeof eslintrcConfig[key] !== "undefined") {
// create the languageOptions key in the flat config
flatConfig.languageOptions = languageOptions;
if (key === "parser") {
debug(`Resolving parser '${languageOptions[key]}' relative to ${resolveConfigRelativeTo}`);
if (eslintrcConfig[key].error) {
throw eslintrcConfig[key].error;
}
languageOptions[key] = eslintrcConfig[key].definition;
continue;
}
// clone any object values that are in the eslintrc config
if (eslintrcConfig[key] && typeof eslintrcConfig[key] === "object") {
languageOptions[key] = {
...eslintrcConfig[key]
};
} else {
languageOptions[key] = eslintrcConfig[key];
}
}
}
// copy over linterOptions
for (const key of linterOptionsKeysToCopy) {
if (key in eslintrcConfig && typeof eslintrcConfig[key] !== "undefined") {
flatConfig.linterOptions = linterOptions;
linterOptions[key] = eslintrcConfig[key];
}
}
// move ecmaVersion a level up
if (languageOptions.parserOptions) {
if ("ecmaVersion" in languageOptions.parserOptions) {
languageOptions.ecmaVersion = languageOptions.parserOptions.ecmaVersion;
delete languageOptions.parserOptions.ecmaVersion;
}
if ("sourceType" in languageOptions.parserOptions) {
languageOptions.sourceType = languageOptions.parserOptions.sourceType;
delete languageOptions.parserOptions.sourceType;
}
// check to see if we even need parserOptions anymore and remove it if not
if (Object.keys(languageOptions.parserOptions).length === 0) {
delete languageOptions.parserOptions;
}
}
// overrides
if (eslintrcConfig.criteria) {
flatConfig.files = [absoluteFilePath => eslintrcConfig.criteria.test(absoluteFilePath)];
}
// translate plugins
if (eslintrcConfig.plugins && typeof eslintrcConfig.plugins === "object") {
debug(`Translating plugins: ${eslintrcConfig.plugins}`);
flatConfig.plugins = {};
for (const pluginName of Object.keys(eslintrcConfig.plugins)) {
debug(`Translating plugin: ${pluginName}`);
debug(`Resolving plugin '${pluginName} relative to ${resolvePluginsRelativeTo}`);
const { definition: plugin, error } = eslintrcConfig.plugins[pluginName];
if (error) {
throw error;
}
flatConfig.plugins[pluginName] = plugin;
// create a config for any processors
if (plugin.processors) {
for (const processorName of Object.keys(plugin.processors)) {
if (processorName.startsWith(".")) {
debug(`Assigning processor: ${pluginName}/${processorName}`);
configs.unshift({
files: [`**/*${processorName}`],
processor: pluginProcessors.get(`${pluginName}/${processorName}`)
});
}
}
}
}
}
// translate env - must come after plugins
if (eslintrcConfig.env && typeof eslintrcConfig.env === "object") {
for (const envName of Object.keys(eslintrcConfig.env)) {
// only add environments that are true
if (eslintrcConfig.env[envName]) {
debug(`Translating environment: ${envName}`);
if (environments.has(envName)) {
// built-in environments should be defined first
configs.unshift(...translateESLintRC({
criteria: eslintrcConfig.criteria,
...environments.get(envName)
}, {
resolveConfigRelativeTo,
resolvePluginsRelativeTo
}));
} else if (pluginEnvironments.has(envName)) {
// if the environment comes from a plugin, it should come after the plugin config
configs.push(...translateESLintRC({
criteria: eslintrcConfig.criteria,
...pluginEnvironments.get(envName)
}, {
resolveConfigRelativeTo,
resolvePluginsRelativeTo
}));
}
}
}
}
// only add if there are actually keys in the config
if (Object.keys(flatConfig).length > 0) {
configs.push(flatConfig);
}
return configs;
}
▪️ for every resolved config from the normalization step
- array of configs
eslintrc - for each one ◌ we do translate it ◌ according to the rules in the function above
Then we add that to the FlatList
which later we gonna spread

- you can see some
propsare just copied language optionskeys need to be moved tolanguageOptionslinterOptionssame thing
handling of ecmaVersion and sourceType
- move one level up
- and check if
parserOptionsis needed anymore

If it was an override config

Set files with a function resolver
- Apparently,
filesdoes support bothstringsandfunction resolvers - (didn’t see that in the doc, maybe I didn’t pay attention well, or I just didn't cross it)
Plugins are set as follows

- The same
nameas theplugin nameis used fornamingtheplugin eslint-plugin-prettier=>prettier(removeeslint-plugin-)
already resolved in the normalization

- that’s the
old convention - For plugins like
@typescript-eslint/eslint-plugin◌ https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/package.json


Plugin source
import all from './configs/all';
import base from './configs/base';
import disableTypeChecked from './configs/disable-type-checked';
import eslintRecommended from './configs/eslint-recommended';
import recommended from './configs/recommended';
import recommendedTypeChecked from './configs/recommended-type-checked';
import strict from './configs/strict';
import strictTypeChecked from './configs/strict-type-checked';
import stylistic from './configs/stylistic';
import stylisticTypeChecked from './configs/stylistic-type-checked';
import rules from './rules';
export = {
configs: {
all,
base,
'disable-type-checked': disableTypeChecked,
'eslint-recommended': eslintRecommended,
recommended,
/** @deprecated - please use "recommended-type-checked" instead. */
'recommended-requiring-type-checking': recommendedTypeChecked,
'recommended-type-checked': recommendedTypeChecked,
strict,
'strict-type-checked': strictTypeChecked,
stylistic,
'stylistic-type-checked': stylisticTypeChecked,
},
rules,
};

/eslint-recommended

/**
* This is a compatibility ruleset that:
* - disables rules from eslint:recommended which are already handled by TypeScript.
* - enables rules that make sense due to TS's typechecking / transpilation.
*/
export = {
overrides: [
{
files: ['*.ts', '*.tsx', '*.mts', '*.cts'],
rules: {
'constructor-super': 'off', // ts(2335) & ts(2377)
'getter-return': 'off', // ts(2378)
'no-const-assign': 'off', // ts(2588)
'no-dupe-args': 'off', // ts(2300)
'no-dupe-class-members': 'off', // ts(2393) & ts(2300)
'no-dupe-keys': 'off', // ts(1117)
'no-func-assign': 'off', // ts(2539)
'no-import-assign': 'off', // ts(2539) & ts(2540)
'no-new-symbol': 'off', // ts(7009)
'no-obj-calls': 'off', // ts(2349)
'no-redeclare': 'off', // ts(2451)
'no-setter-return': 'off', // ts(2408)
'no-this-before-super': 'off', // ts(2376)
'no-undef': 'off', // ts(2304)
'no-unreachable': 'off', // ts(7027)
'no-unsafe-negation': 'off', // ts(2365) & ts(2360) & ts(2358)
'no-var': 'error', // ts transpiles let/const to var, so no need for vars any more
'prefer-const': 'error', // ts provides better types with const
'prefer-rest-params': 'error', // ts provides better types with rest args over arguments
'prefer-spread': 'error', // ts transpiles spread to apply, so no need for manual apply
},
},
],
};
- For name resolution, it would be
◌
@typescript-eslint/eslint-plugin=>typescriptEslint
Here is the normalization logic for plugins
node_modules/@eslint/eslintrc/lib/shared/naming.js

function normalizePackageName(name, prefix) {
let normalizedName = name;
/**
* On Windows, name can come in with Windows slashes instead of Unix slashes.
* Normalize to Unix first to avoid errors later on.
* https://github.com/eslint/eslint/issues/5644
*/
if (normalizedName.includes("\\")) {
normalizedName = normalizedName.replace(/\\/gu, "/");
}
if (normalizedName.charAt(0) === "@") {
/**
* it's a scoped package
* package name is the prefix, or just a username
*/
const scopedPackageShortcutRegex = new RegExp(`^(@[^/]+)(?:/(?:${prefix})?)?$`, "u"),
scopedPackageNameRegex = new RegExp(`^${prefix}(-|$)`, "u");
if (scopedPackageShortcutRegex.test(normalizedName)) {
normalizedName = normalizedName.replace(scopedPackageShortcutRegex, `$1/${prefix}`);
} else if (!scopedPackageNameRegex.test(normalizedName.split("/")[1])) {
/**
* for scoped packages, insert the prefix after the first / unless
* the path is already @scope/eslint or @scope/eslint-xxx-yyy
*/
normalizedName = normalizedName.replace(/^@([^/]+)\/(.*)$/u, `@$1/${prefix}-$2`);
}
} else if (!normalizedName.startsWith(`${prefix}-`)) {
normalizedName = `${prefix}-${normalizedName}`;
}
return normalizedName;
}

if (normalizedName.charAt(0) === "@") {
/**
* it's a scoped package
* package name is the prefix, or just a username
*/
const scopedPackageShortcutRegex = new RegExp(`^(@[^/]+)(?:/(?:${prefix})?)?$`, "u"),
scopedPackageNameRegex = new RegExp(`^${prefix}(-|$)`, "u");
if (scopedPackageShortcutRegex.test(normalizedName)) {
normalizedName = normalizedName.replace(scopedPackageShortcutRegex, `$1/${prefix}`);
} else if (!scopedPackageNameRegex.test(normalizedName.split("/")[1])) {
/**
* for scoped packages, insert the prefix after the first / unless
* the path is already @scope/eslint or @scope/eslint-xxx-yyy
*/
normalizedName = normalizedName.replace(/^@([^/]+)\/(.*)$/u, `@$1/${prefix}-$2`);
}
} else if (!normalizedName.startsWith(`${prefix}-`)) {
normalizedName = `${prefix}-${normalizedName}`;
}
- if
scoped package◌ is ita domain? ◌ or just ausername?
/**
* for scoped packages, insert the prefix after the first / unless
* the path is already @scope/eslint or @scope/eslint-xxx-yyy
*/
- ◌
@{domain}/eslint-plugin◌ or ◌@{username}/eslint-plugin-{some_name}
That will normalize the package name
- For
shorthandwhich is used in theflat Config

function getShorthandName(fullname, prefix) {
if (fullname[0] === "@") {
let matchResult = new RegExp(`^(@[^/]+)/${prefix}$`, "u").exec(fullname);
if (matchResult) {
return matchResult[1];
}
matchResult = new RegExp(`^(@[^/]+)/${prefix}-(.+)$`, "u").exec(fullname);
if (matchResult) {
return `${matchResult[1]}/${matchResult[2]}`;
}
} else if (fullname.startsWith(`${prefix}-`)) {
return fullname.slice(prefix.length + 1);
}
return fullname;
}
- if
@{domain}/eslint-plugin◌@{domain} - Otherwise if
@{username}/eslint-plugin-{some_name}◌@{username}/{some_name}

- plugin name
@typescript-eslintwith@
What does it resolve to in normalization ???

- lets simply see

- setting
break pointafter theinstallation of the module - and lets roll

errorneeded to passstringforcompat.plugins()

The name is @typescript-eslint

let’s see in the flat config

- it’s just the same
◌
@typescript-eslint
✨ Ok cool what are the lessons 🔥🔥🔥
✨ Naming convention when it comes to translating old plugins 🔥
eslint-plugin-{name}◌ name{name}@{scope}/eslint-plugin◌ name@{scope}- Otherwise if
@{scope}/eslint-plugin-{some_name}◌ name@{scope}/{some_name}
Examples:
eslint-plugin-prettier◌ nameprettier@typescript-eslint/eslint-plugin◌ name@typescript-eslint
✨ Plugins that disable or override other plugins or configs settings
- The one that disables others. Need to go last. Or after what they disable (
disabling override) - The one that acts as a base
extends. They go first.
Example eslint-config-prettier
- in doc ◌ https://github.com/prettier/eslint-config-prettier#eslintconfigjs-flat-config-plugin-caveat

- saying you can name
typescriptEslint - I would say name it
"@typescript-eslint": typescriptEslintPlugin◌ it’s whatFlatCompatdo ◌ less confusing to people ◌ natural to match with the conventions

- you can also verify that in
rules🔥 👆 ◌ that’s what is used ◌ You can navigate fast to therulesusingCMD|CTRL + CLICK - but if
typescriptEslintworks - that means somehow
eslintis converting that to match@typescript-eslint◌ basically, adding@andcamel caseto- - Personally, I will go with the full name
"@typescript-eslint": typescriptEslintPlugin- and directly I would go check the rules 🔥
- and see what they used
ctrl|cmd+Fwhen you are searching to handle it fast
✨ How to translate and use Eslint modules in FlatConfig 🔥
▪ Know what is involved
Plugin,rules,extends,overrides,mixture◌ If it’sextendsyou can check the package source. And determine what is used.
▪️ Know the properties mapping from eslintrc to FlatConfig
- Code Link to the translation logic in
FlatCompattool (as ref) - TK [LINK TO main article]
- The ones that get directly copied
◌ First level
directly copied-[“settings”, “rules”, “processor”]◌Language Options keysthat get moved tolanguageOptions-[“globals”, “parser”, “parserOptions”]▶︎ forparseryou have to copy thedefinition
languageOptions[key] = eslintrcConfig[key].definition; // key == 'parser'
▶︎ For parserOptions
ecmaVersionandsourceTypeneed to go intolanguageOptionsdirectly- In
old eslintrcthey were inparserOptions. InFlatConfigthey are inlanguageOptions
◌ Linter Options keys that get moved to linterOptions
[“noInlineConfig”, “reportUnusedDisableDirectives”]
▪️ For plugins
- Set plugins in
pluginsprop object. - Make sure to name them following the name convention mentioned above
- If the plugin does hold special
processorsperfiles(name starts with.). Then you can add at the beginning ofFlat config. Object as follows:
[
{
files: [`**/*${processorName}`],
processor: pluginProcessors.get(`${pluginName}/${processorName}`)
},
// ...
]
- Also, that’s the way if you need to use any custom processor provided by a plugin
<pluginName>/<processorName>.
▪️ For env
- You have to set
globalsinlanguageOptions.globalsand use theglobalspackage. And you can get sense and examples from the eslintrc built-in environments. And you setecmaVersioninlanguageOptions.ecmaVersion.ecmaFeatureswould go inlanguageOptions.parserOptions.ecmaFeatures. That gives you a good sense.
▪️ For overrides
overridesalready follow a structure similar toFlatConfig- You would basically, keep the same
order. And make the sametransformationas in the above. - Keep in mind the
orderand when there is aconfigurationthat disables others, it would need to come after what it would disable.
For extends
- You have to check the module. The best way is to
CMD|CTRL + CLICKinvscodeand it would take you to the module code source. There check the structure. And determine the parts that are explained above. - For all the parts that make sense and can go in a particular part of your
FlatConfig. Directly add them granularly to the right part. (A great example is Flat config: Setting up Eslint and Prettier with eslint-prettier-plugin article). ◌ For example instead of[{ <base> }, {}], You do[{ directly granular object merging }]when it makes sense. - And you can use a
separateconfigwith the rightoverridingorder depending on the thing. This would be the way to go, when there is a need for deep merging. That you can’t simply do through thegranularway.
Check the full practical example
- Flat config: Setting up Eslint and Prettier with eslint-prettier-plugin
◌ For using
FlatCompatutility. CheckWhat about if you want to do some things for just some filestitle at the end - How to use
FlatCompatand set the config for onlysome files
patterns in the code ✨
generatorsrecursivitywithgenerators- data
normalization moduleorplugin resolutionwithinnormalizationDependencymanagement orresolutionData transformationandremapping
My Other Eslint related articles ✨
▪️ Flat config system
- ✨ Eslint flat config and new system an ultimate deep dive 2023 ✨ 🔥
- Eslint: how to name plugins 🔥
-
- All below
▪️ Eslint prettier
- Flat config: Setting up Eslint and Prettier with eslint-prettier-plugin 🔥
- eslint-plugin-prettier vs eslint-config-prettier 🔥
- How does eslint-config-prettier works ? 🔥
▪️ String, name resolution (plugin, config)
메타데이터
- post_id
- 83b35d1e2f75
- slug
- eslint-flatcompat-utility-and-its-work-and-magic-a-deep-dive-that-demystifies-flat-config-and-83b35d1e2f75
- url
- https://medium.com/@allalmohamedlamine/eslint-flatcompat-utility-and-its-work-and-magic-a-deep-dive-that-demystifies-flat-config-and-83b35d1e2f75
- canonical_url
- https://medium.com/@allalmohamedlamine/eslint-flatcompat-utility-and-its-work-and-magic-a-deep-dive-that-demystifies-flat-config-and-83b35d1e2f75
- author_url
- https://medium.com/@allalmohamedlamine
- status
- ok
- fetched_at
- 2026-07-28 16:12:54