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 lessons
section 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
GitHub
with the code source - You will get to see how
FlatCompat
works Normalization pattern
with a great open-source example- How to transform
Eslintrc config
toFlatConfig
- Eslint
modules
Nameresolution algorithm
- A demonstration of how you use the
debugger
invscode
toanalyze
andunderstand
thecode
and 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
eslint
config
◌ And it would make magic totranslate
it toflat config
✨ Before starting, things to establish
▪️ config
- take one
plugin config
at a time - And
translate
somethingequiv
forflat 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
normalization
when it comes toprocessing
andparsing
config
◌ 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
debugger
attached and pause at ourbreakpoint
- using the
debugger
like 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 in
in this time
step in
again
- I can add
breakpoints
now
◌ You can always addbreakpoints
while 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 in
we 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 thevalidation
part ofnormalization
◌ And it’s acritical
andcommon
step innormalization
- we can see the config passed to it
- we can see the
validate schema
- And basically, it’s
eslintrc config
schema - 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 step
over 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 tester
seems totest
foroverride 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
lib
folder
▪️ We see all similar functions
- for
load
functions - 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 thepower
ofgood 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 sugar
foriterators
◌ just likeasync await
◌Generators
(if not familiar), are popular across all languages
◌ Andyield
is the equiv ofawait
orreturn
-yield
return the value and pause the execution. Untilgenerator.next()
is called again. Where the execution will resume from that lastyield
pause place in the code and that’s why it’s a syntaxic sugar likeawait
. Thegenerator
syntax
allows the creation of aniterator
easily (just likeawait
does withpromises
).
- AGenerator
is afunction
. In whichyield
is used. Thatreturn
aniterator
. Theiterator
withnext()
API is used toiterate
through the execution context (defined by theyield
points). Eachnext()
call will start theexecution
from the lastyield
point to the next one and pause. Till the nextnext()
call will be made. Ifreturn
is encountered. Theiterator
comes to an end.
- In theloop
aboveyield
will return the value ofloadExtends()
and pause the execution. Untilgenerator.next()
is called. Where the execution will continue from that sameyield
point.
- gen.next() => exec till encounteryield
=>pause
,return
val
— once we callgen.next()
again => resume execution fromlast
yield
(pause point) => run until the encounter of anotheryield
=>pause
,return
val => …
for of
supportiterators
for of
is working by each time calling
◌elements.next()
🔥
◌ when that happens => our function will execute 🔥
◌ until it reaches ayield
🔥- ◌ at that point as with
await
orreturn
◌ The value ofnext()
is returned
◌ in this case it will be stored inelement
variable
◌ our loop block will execute
◌ Then againof
will go 🔥
◌ and.next()
called again 🔥
◌ however this time
▶︎ the execution of the function will start just after the line containingyield
🔥
▶︎generator
andyield
allow abreakpoint
forstopping
▶ ︎and later it would resume from there
▶︎ check out a course aboutgenerators
or [my article about it][Will be added later, TK]
- check how to translategenerators
toiterators
- ✨ The best way also, to play with that is to go to babel playground 🔥
- and use agenerator
, for example, the function above
- And check theoutput
initerator
format
- basically, what thesyntax-ic sugar
translate to
You can see the iterator
and the usage of switch
and next
context
to hold which block to run next
….
- Function in
generator
format
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
Babel
Playgroundtranspilation
compiled
fornodejs
ES5
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 in
let’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
block
we get to know howextends
works andeslint
convention when it comes to it
▪️ eslint:
- by
eslint
extends
▪ ️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 a package 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
FlatCompat
tool 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
plugin
loaded, ready to be used
- All this, to get the
plugin
config
data - Then we have to
normalize
it - When you
normalize
data, you can match and count on it
◌Data
normalization
ease up working withdata
. By creatingstructure
. And oneformat
.
- You know the
function
- And we are using
recursivity
- And you can know why?
◌ In case ofmodules
that would haveextends
and otherprops
as well
-M1 (extend (M2 extend (M3 …)))
◌ So all needs to happenrecursively
I'm passing
recursivity
kicking in
this time extends: ["prettier"]
- => will be resolved to
eslint-config-prettier
which is a fully different package
- we are going again
◌ same trip
again, an extends
a sharable config
resolution
topackage
name done- let's get the
package
path
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
plugin
will berequired
- and the whole
normalization
will be applied as well - we move on
Again the same normalization
function (generator
)
recursivity
- This time the
object
hasrules
only - So no more
iterations
after 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
parser
skip
- 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
normalization
finish Transformation
will go
▪️ plugins
here are the actual loaded plugins
- nodejs
require
◌ ++
▪️ plugins
, rules
are correctly loaded
plugins
loaded with actualobjets
notstring
◌ exactly whatflat config
require
- same thing as before
Done normalizing
recursivity
upward
◌ I skipped astep
Hola back
We could have skipped all that journey
- I did it only to showcase
debugging
usage - And for some
curiosity
◌ checking the code
▪ ️Two configs are loaded fully
eslint-plugin-prettier
plugin
◌ withplugin
loaded correctlyeslint-config-prettier
rules 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 over
instead) 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
props
are just copied language options
keys need to be moved tolanguageOptions
linterOptions
same thing
handling of ecmaVersion
and sourceType
- move one level up
- and check if
parserOptions
is needed anymore
If it was an override
config
Set files
with a function resolver
- Apparently,
files
does support bothstrings
andfunction 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
name
as theplugin name
is used fornaming
theplugin
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
shorthand
which 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-eslint
with@
What does it resolve to in normalization
???
- lets simply see
- setting
break point
after theinstallation of the module
- and lets roll
error
needed to passstring
forcompat.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
- saying you can name
typescriptEslint
- I would say name it
"@typescript-eslint": typescriptEslintPlugin
◌ it’s whatFlatCompat
do
◌ 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 therules
usingCMD|CTRL + CLICK
- but if
typescriptEslint
works - that means somehow
eslint
is converting that to match@typescript-eslint
◌ basically, adding@
andcamel case
to-
- 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
+F
when 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’sextends
you 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
FlatCompat
tool (as ref) - TK [LINK TO main article]
- The ones that get directly copied
◌ First leveldirectly copied
-[“settings”, “rules”, “processor”]
◌Language Options keys
that get moved tolanguageOptions
-[“globals”, “parser”, “parserOptions”]
▶︎ forparser
you have to copy thedefinition
languageOptions[key] = eslintrcConfig[key].definition; // key == 'parser'
▶︎ For parserOptions
ecmaVersion
andsourceType
need to go intolanguageOptions
directly- In
old eslintrc
they were inparserOptions
. InFlatConfig
they are inlanguageOptions
◌ Linter Options keys
that get moved to linterOptions
- [“noInlineConfig”, “reportUnusedDisableDirectives”]
▪️ For plugins
- Set plugins in
plugins
prop object. - Make sure to name them following the name convention mentioned above
- If the plugin does hold special
processors
perfiles
(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
globals
inlanguageOptions.globals
and use theglobals
package. And you can get sense and examples from the eslintrc built-in environments. And you setecmaVersion
inlanguageOptions.ecmaVersion
.ecmaFeatures
would go inlanguageOptions.parserOptions.ecmaFeatures
. That gives you a good sense.
▪️ For overrides
overrides
already follow a structure similar toFlatConfig
- You would basically, keep the same
order
. And make the sametransformation
as in the above. - Keep in mind the
order
and when there is aconfiguration
that 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 + CLICK
invscode
and 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
separate
config
with the rightoverriding
order 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 thegranular
way.
Check the full practical example
- Flat config: Setting up Eslint and Prettier with eslint-prettier-plugin
◌ For usingFlatCompat
utility. CheckWhat about if you want to do some things for just some files
title at the end
- How to useFlatCompat
and set the config for onlysome files
patterns in the code ✨
generators
recursivity
withgenerators
- data
normalization
module
orplugin resolution
withinnormalization
Dependency
management orresolution
Data transformation
andremapping
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)