From 5e5fc4c14c6eacf0016fe9579faa3ba139170b02 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Sat, 9 Mar 2024 12:11:43 -0600 Subject: [PATCH 01/23] feat: Add support for TypeScript config files --- .../2024-support-ts-config-files/README.md | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 designs/2024-support-ts-config-files/README.md diff --git a/designs/2024-support-ts-config-files/README.md b/designs/2024-support-ts-config-files/README.md new file mode 100644 index 00000000..c6c4523d --- /dev/null +++ b/designs/2024-support-ts-config-files/README.md @@ -0,0 +1,130 @@ +- Repo: eslint/eslint +- Start Date: 2023-03-09 +- RFC PR: (leave this empty, to be filled in later) +- Authors: [Arya Emami](https://github.com/aryaemami59) + +# Add Support for TypeScript Config Files + +## Summary + +Add support for TypeScript config files (`eslint.config.ts`, `eslint.config.mts`, `eslint.config.cts`) + + + +## Motivation + + + +The primary motivation for adding support for TypeScript configuration files to ESLint is to enhance the developer experience and accommodate the evolving JavaScript ecosystem. As TypeScript's popularity continues to grow, more projects are adopting TypeScript not only for their source code but also for their configuration files. This shift is driven by TypeScript's ability to provide compile-time type checks and IntelliSense. By supporting `eslint.config.ts`, `eslint.config.mts`, and `eslint.config.cts`, ESLint will offer first-class support to TypeScript users, allowing them to leverage these benefits directly within their ESLint configuration. + +## Detailed Design + + + +The goal is to seamlessly support TypeScript configuration files in ESLint. To achieve this, ESLint will need to recognize and parse TypeScript configuration files in the same way it does for JavaScript configuration files. This will involve creating the configuration file resolution logic to recognize `.ts`, `.mts`, and `.cts` files as valid ESLint configuration files. We will need to treat `eslint.config.mts` files as ECMAScript Modules (ESM) and `eslint.config.cts` files as CommonJS (CJS). The module resolution for `eslint.config.ts` files will depend on the `type` field in the nearest `package.json`. The maintainers of ESLint have raised some valid concerns some of which include: + +- There should not be extra overhead for JavaScript users. This means this change should not have a significant impact (if any at all) affecting users who use plain JavaScript config files. +- The external tools that are used to parse the config files written in TypeScript should not create side effects. Specifically, it is imperative that these tools do not interfere with Node.js's native module resolution system by hooking into or altering the standard `import/require` mechanisms. This means tools like [`ts-node`](https://github.com/TypeStrong/ts-node) and [`tsx`](https://github.com/privatenumber/tsx) might not be suitable for this purpose. + +So far the tool that seems to be the most suitable for this purpose is [`jiti`](https://github.com/unjs/jiti). It does not introduce side effects and performs well, demonstrating its reliability. It also seems to be more battle-tested given some established frameworks such as [Nuxt](https://github.com/nuxt/nuxt) and [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss) have been using it to load their configuration files. + +## Documentation + + + +The documentation for this feature will be added to the [ESLint User Guide](https://eslint.org/docs/user-guide/configuring) page. The documentation will explain how to use TypeScript configuration files and the differences between JavaScript and TypeScript configuration files. + +## Drawbacks + + + +This change will most likely require at least one external tool as either a dependency or a peer dependency. + +## Backwards Compatibility Analysis + + + +This goal is to minimize the disruption to existing users. The primary focus is to ensure that the existing JavaScript configuration files continue to work as expected. The changes will only affect TypeScript users who are using TypeScript configuration files. The changes will not affect the existing JavaScript configuration files. + +## Alternatives + + + +## Open Questions + + + +1. How is caching going to work with TypeScript config files? +2. Should we look at the nearest `tsconfig.json` file to determine the module resolution for `eslint.config.ts` files? Most likely not, but it's worth considering. +3. Should we allow some sort of interoperability between JavaScript and TypeScript configuration files? For example, should we allow a TypeScript configuration file to extend a JavaScript configuration file and vice versa? +4. Should we allow `eslint.config.ts` to be able to use `export default` as well as `module.exports` (might be related to [TypeScript's automatic Module Detection](https://www.typescriptlang.org/tsconfig#moduleDetection))? + +## Help Needed + + + +I will be implementing this feature. I will need help from the team to review the code and provide feedback. + +## Frequently Asked Questions + + + +## Related Discussions + + + +[This PR](https://github.com/eslint/eslint/pull/18134) is related to this RFC. From 50676fa538554d06716f0b1d5f70a9331c4fe2a6 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Sat, 9 Mar 2024 17:58:43 -0600 Subject: [PATCH 02/23] Update start date in `README.md` --- designs/2024-support-ts-config-files/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2024-support-ts-config-files/README.md b/designs/2024-support-ts-config-files/README.md index c6c4523d..6f7f3c08 100644 --- a/designs/2024-support-ts-config-files/README.md +++ b/designs/2024-support-ts-config-files/README.md @@ -1,5 +1,5 @@ - Repo: eslint/eslint -- Start Date: 2023-03-09 +- Start Date: 2024-03-09 - RFC PR: (leave this empty, to be filled in later) - Authors: [Arya Emami](https://github.com/aryaemami59) From 9ab30b52c5294655c24e8e3613ad1d5cf8e7c1a1 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Mon, 11 Mar 2024 22:05:50 -0500 Subject: [PATCH 03/23] Add RFC PR --- designs/2024-support-ts-config-files/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2024-support-ts-config-files/README.md b/designs/2024-support-ts-config-files/README.md index 6f7f3c08..09da8007 100644 --- a/designs/2024-support-ts-config-files/README.md +++ b/designs/2024-support-ts-config-files/README.md @@ -1,6 +1,6 @@ - Repo: eslint/eslint - Start Date: 2024-03-09 -- RFC PR: (leave this empty, to be filled in later) +- RFC PR: - Authors: [Arya Emami](https://github.com/aryaemami59) # Add Support for TypeScript Config Files From c0c0714adcb629969410ae879ac5fb6c9aa25623 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Mon, 11 Mar 2024 22:37:06 -0500 Subject: [PATCH 04/23] Link prior RFC and issue regarding support for `.eslintrc.ts` files. --- designs/2024-support-ts-config-files/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/designs/2024-support-ts-config-files/README.md b/designs/2024-support-ts-config-files/README.md index 09da8007..5fbd015b 100644 --- a/designs/2024-support-ts-config-files/README.md +++ b/designs/2024-support-ts-config-files/README.md @@ -128,3 +128,5 @@ I will be implementing this feature. I will need help from the team to review th --> [This PR](https://github.com/eslint/eslint/pull/18134) is related to this RFC. +[Prior Discussion](https://github.com/eslint/rfcs/pull/50) related to supporting `.eslintrc.ts` files. +[Prior Issue](https://github.com/eslint/eslint/issues/12078) related to supporting `.eslintrc.ts` files. From 247393ff06e78b7cb0901ba2718c6f6b310ae15c Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Mon, 11 Mar 2024 22:52:06 -0500 Subject: [PATCH 05/23] Add `External References` section --- designs/2024-support-ts-config-files/README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/designs/2024-support-ts-config-files/README.md b/designs/2024-support-ts-config-files/README.md index 5fbd015b..5de45480 100644 --- a/designs/2024-support-ts-config-files/README.md +++ b/designs/2024-support-ts-config-files/README.md @@ -34,7 +34,7 @@ The goal is to seamlessly support TypeScript configuration files in ESLint. To a - There should not be extra overhead for JavaScript users. This means this change should not have a significant impact (if any at all) affecting users who use plain JavaScript config files. - The external tools that are used to parse the config files written in TypeScript should not create side effects. Specifically, it is imperative that these tools do not interfere with Node.js's native module resolution system by hooking into or altering the standard `import/require` mechanisms. This means tools like [`ts-node`](https://github.com/TypeStrong/ts-node) and [`tsx`](https://github.com/privatenumber/tsx) might not be suitable for this purpose. -So far the tool that seems to be the most suitable for this purpose is [`jiti`](https://github.com/unjs/jiti). It does not introduce side effects and performs well, demonstrating its reliability. It also seems to be more battle-tested given some established frameworks such as [Nuxt](https://github.com/nuxt/nuxt) and [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss) have been using it to load their configuration files. +So far the tool that seems to be the most suitable for this purpose is [`jiti`](https://www.npmjs.com/package/jiti). It does not introduce side effects and performs well, demonstrating its reliability. It also seems to be more battle-tested given some established frameworks such as [Nuxt](https://github.com/nuxt/nuxt) and [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss) have been using it to load their configuration files. ## Documentation @@ -130,3 +130,13 @@ I will be implementing this feature. I will need help from the team to review th [This PR](https://github.com/eslint/eslint/pull/18134) is related to this RFC. [Prior Discussion](https://github.com/eslint/rfcs/pull/50) related to supporting `.eslintrc.ts` files. [Prior Issue](https://github.com/eslint/eslint/issues/12078) related to supporting `.eslintrc.ts` files. + +## External References + +- [`jiti` on NPM](https://www.npmjs.com/package/jiti) +- [`jiti` on Github](https://github.com/unjs/jiti) +- [`tsx` on NPM](https://www.npmjs.com/package/tsx) +- [`tsx` on Github](https://github.com/privatenumber/tsx) +- [`ts-node` on NPM](https://www.npmjs.com/package/ts-node) +- [`ts-node` on Github](https://github.com/TypeStrong/ts-node) +- [`ts-node` Docs](https://typestrong.org/ts-node) From d13d556ef053acdcd4a70ee0dfa601dfa5d96bc4 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Mon, 11 Mar 2024 23:06:50 -0500 Subject: [PATCH 06/23] Add alternatives considered for parsing TypeScript configuration files --- designs/2024-support-ts-config-files/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/designs/2024-support-ts-config-files/README.md b/designs/2024-support-ts-config-files/README.md index 5de45480..7af4d27e 100644 --- a/designs/2024-support-ts-config-files/README.md +++ b/designs/2024-support-ts-config-files/README.md @@ -79,6 +79,14 @@ This goal is to minimize the disruption to existing users. The primary focus is projects have already implemented a similar feature. --> +While developing this feature, we considered the following alternatives: + +1. Using [`ts-node`](https://github.com/TypeStrong/ts-node) to parse TypeScript configuration files. This approach proved to be problematic because [`ts-node`](https://github.com/TypeStrong/ts-node) hooks into Node.js's native module resolution system, which could potentially cause side effects. + +2. Using [`tsx`](https://github.com/privatenumber/tsx) to parse TypeScript configuration files. This approach also proved to be problematic because [`tsx`](https://github.com/privatenumber/tsx) hooks into Node.js's native module resolution system, which could potentially cause side effects. + +3. Using [TypeScript's `transpileModule()`](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API#a-simple-transform-function) to parse TypeScript configuration files. This approach proved to be problematic because it requires a significant amount of overhead and is not suitable for this purpose. + ## Open Questions -[This PR](https://github.com/eslint/eslint/pull/18134) is related to this RFC. -[Prior Discussion](https://github.com/eslint/rfcs/pull/50) related to supporting `.eslintrc.ts` files. -[Prior Issue](https://github.com/eslint/eslint/issues/12078) related to supporting `.eslintrc.ts` files. +- [This PR](https://github.com/eslint/eslint/pull/18134) is related to this RFC. +- [Prior Discussion](https://github.com/eslint/rfcs/pull/50) related to supporting `.eslintrc.ts` files. +- [Prior Issue](https://github.com/eslint/eslint/issues/12078) related to supporting `.eslintrc.ts` files. ## External References From d5e16dbdabc653c6260d36ff85ca484da2e2bbda Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Tue, 12 Mar 2024 00:06:37 -0500 Subject: [PATCH 10/23] Inform about the possibility of type checking in `.js` config files --- .../2024-support-ts-config-files/README.md | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/designs/2024-support-ts-config-files/README.md b/designs/2024-support-ts-config-files/README.md index d49b7cc4..eb5227d2 100644 --- a/designs/2024-support-ts-config-files/README.md +++ b/designs/2024-support-ts-config-files/README.md @@ -92,6 +92,44 @@ const config: Linter.FlatConfig[] = [ export default config ``` +It is worth noting that you can already perform some type-checking with the [`checkJs`](https://www.typescriptlang.org/tsconfig#checkJs) TypeScript option and a JavaScript config file. Here's an example: + +with `eslint.config.mjs` or (`eslint.config.js` + `"type": "module"` in the nearest `package.json`): + +```js +import eslint from "@eslint/js" + +/** @type {import('eslint').Linter.FlatConfig[]} */ +const config = [ + eslint.configs.recommended, + { + rules: { + "no-console": [0], + }, + }, +] + +export default config +``` + +with `eslint.config.cjs` or (`eslint.config.js` without `"type": "module"` in the nearest `package.json`): + +```js +const eslint = require("@eslint/js") + +/** @type {import('eslint').Linter.FlatConfig[]} */ +const config = [ + eslint.configs.recommended, + { + rules: { + "no-console": [0], + }, + }, +] + +module.exports = config +``` + ## Documentation -1. How is caching going to work with TypeScript config files? +1. How is caching going to work with TypeScript config files? We only cache the computed result of loading a config file, so I don't think this should be a problem. 2. Should we look at the nearest `tsconfig.json` file to determine the module resolution for `eslint.config.ts` files? Most likely not, but it's worth considering. 3. Should we allow some sort of interoperability between JavaScript and TypeScript configuration files? For example, should we allow a TypeScript configuration file to extend a JavaScript configuration file and vice versa? 4. Should we allow `eslint.config.ts` to be able to use `export default` as well as `module.exports` (might be related to [TypeScript's automatic Module Detection](https://www.typescriptlang.org/tsconfig#moduleDetection))? From ef591fcefb07df78854a1d30d6beaf186f290c3e Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Thu, 28 Mar 2024 17:32:01 -0500 Subject: [PATCH 15/23] Add answer about interoperability question --- designs/2024-support-ts-config-files/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2024-support-ts-config-files/README.md b/designs/2024-support-ts-config-files/README.md index 444547e1..061b9c16 100644 --- a/designs/2024-support-ts-config-files/README.md +++ b/designs/2024-support-ts-config-files/README.md @@ -196,7 +196,7 @@ While developing this feature, we considered the following alternatives: 1. How is caching going to work with TypeScript config files? We only cache the computed result of loading a config file, so I don't think this should be a problem. 2. Should we look at the nearest `tsconfig.json` file to determine the module resolution for `eslint.config.ts` files? Most likely not, but it's worth considering. -3. Should we allow some sort of interoperability between JavaScript and TypeScript configuration files? For example, should we allow a TypeScript configuration file to extend a JavaScript configuration file and vice versa? +3. Should we allow some sort of interoperability between JavaScript and TypeScript configuration files? For example, should we allow a TypeScript configuration file to extend a JavaScript configuration file and vice versa? I don't believe this is an issue as the `extends` key isn't supported. Users just use `import` to load anything else they need. 4. Should we allow `eslint.config.ts` to be able to use `export default` as well as `module.exports` (might be related to [TypeScript's automatic Module Detection](https://www.typescriptlang.org/tsconfig#moduleDetection))? 5. Tools like [Vitest](https://github.com/vitest-dev/vitest) export a [`defineConfig`](https://vitest.dev/config/file.html#managing-vitest-config-file) function to make it easier to write configuration files in TypeScript. Should we consider doing something similar for ESLint? 6. How does the feature interact with the [CLI option](https://eslint.org/docs/latest/use/command-line-interface#options) [`--config`](https://eslint.org/docs/latest/use/command-line-interface#-c---config) for specifying a config file? It doesn't behave any differently, same as before. You can do `eslint . --config=eslint.config.ts` or `eslint . -c eslint.config.ts` and they just work. Same as with a `eslint.config.js` file. From 7dfea391734baf3b15f3c99e3e58cb7dcb6ad607 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Thu, 28 Mar 2024 18:14:51 -0500 Subject: [PATCH 16/23] Add how and where we use `jiti` in the Detailed Design` section --- .../2024-support-ts-config-files/README.md | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/designs/2024-support-ts-config-files/README.md b/designs/2024-support-ts-config-files/README.md index 061b9c16..de377099 100644 --- a/designs/2024-support-ts-config-files/README.md +++ b/designs/2024-support-ts-config-files/README.md @@ -36,6 +36,83 @@ The goal is to seamlessly support TypeScript configuration files in ESLint. To a So far the tool that seems to be the most suitable for this purpose is [`jiti`](https://www.npmjs.com/package/jiti). It does not introduce side effects and performs well, demonstrating its reliability. It also seems to be more battle-tested given some established frameworks such as [Nuxt](https://github.com/nuxt/nuxt), [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss) and [Docusaurus](https://github.com/facebook/docusaurus) have been using it to load their configuration files. +- Here is how we would use [`jiti`](https://www.npmjs.com/package/jiti) to load TypeScript configuration files: + +inside [`lib/eslint/eslint.js`](https://github.com/eslint/eslint/blob/main/lib/eslint/eslint.js): + +```js +/** + * Check if the file is a TypeScript file. + * @param {string} filePath The file path to check. + * @returns {boolean} `true` if the file is a TypeScript file, `false` if it's not. + */ +function isFileTS(filePath) { + const fileExtension = path.extname(filePath); + + return fileExtension.endsWith("ts"); +} + +/** + * Load the config array from the given filename. + * @param {string} filePath The filename to load from. + * @returns {Promise} The config loaded from the config file. + */ +async function loadFlatConfigFile(filePath) { + debug(`Loading config from ${filePath}`); + + const fileURL = pathToFileURL(filePath); + + debug(`Config file URL is ${fileURL}`); + + const mtime = (await fs.stat(filePath)).mtime.getTime(); + + /* + * Append a query with the config file's modification time (`mtime`) in order + * to import the current version of the config file. Without the query, `import()` would + * cache the config file module by the pathname only, and then always return + * the same version (the one that was actual when the module was imported for the first time). + * + * This ensures that the config file module is loaded and executed again + * if it has been changed since the last time it was imported. + * If it hasn't been changed, `import()` will just return the cached version. + * + * Note that we should not overuse queries (e.g., by appending the current time + * to always reload the config file module) as that could cause memory leaks + * because entries are never removed from the import cache. + */ + fileURL.searchParams.append("mtime", mtime); + + /* + * With queries, we can bypass the import cache. However, when import-ing a CJS module, + * Node.js uses the require infrastructure under the hood. That includes the require cache, + * which caches the config file module by its file path (queries have no effect). + * Therefore, we also need to clear the require cache before importing the config file module. + * In order to get the same behavior with ESM and CJS config files, in particular - to reload + * the config file only if it has been changed, we track file modification times and clear + * the require cache only if the file has been changed. + */ + if (importedConfigFileModificationTime.get(filePath) !== mtime) { + delete require.cache[filePath]; + } + + const isTS = isFileTS(filePath); + + if (isTS) { + const jiti = (await import("jiti")).default(__filename, { interopDefault: true }); + + const config = jiti(fileURL.href); + + return config; + } + + const config = (await import(fileURL.href)).default; + + importedConfigFileModificationTime.set(filePath, mtime); + + return config; +} +``` + ## Examples with `eslint.config.mts` file: From de7e87ab2c80997c3cf7c653db215a7d5b1cd7d1 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Tue, 2 Apr 2024 06:11:25 -0500 Subject: [PATCH 17/23] Add missing section related to `importedConfigFileModificationTime` --- designs/2024-support-ts-config-files/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/designs/2024-support-ts-config-files/README.md b/designs/2024-support-ts-config-files/README.md index de377099..6525adb5 100644 --- a/designs/2024-support-ts-config-files/README.md +++ b/designs/2024-support-ts-config-files/README.md @@ -102,6 +102,8 @@ async function loadFlatConfigFile(filePath) { const config = jiti(fileURL.href); + importedConfigFileModificationTime.set(filePath, mtime); + return config; } From fb612ed454a830edf9d1704ee9e936791653d237 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Tue, 2 Apr 2024 15:51:04 -0500 Subject: [PATCH 18/23] Enable `esmResolve` for `jiti` --- designs/2024-support-ts-config-files/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2024-support-ts-config-files/README.md b/designs/2024-support-ts-config-files/README.md index 6525adb5..427c9529 100644 --- a/designs/2024-support-ts-config-files/README.md +++ b/designs/2024-support-ts-config-files/README.md @@ -98,7 +98,7 @@ async function loadFlatConfigFile(filePath) { const isTS = isFileTS(filePath); if (isTS) { - const jiti = (await import("jiti")).default(__filename, { interopDefault: true }); + const jiti = (await import("jiti")).default(__filename, { interopDefault: true, esmResolve: true }); const config = jiti(fileURL.href); From f60a15cc4fc38202d959aefe363f51217eed5d6b Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Thu, 4 Apr 2024 17:53:00 -0500 Subject: [PATCH 19/23] Update `Detailed Design` section --- .../2024-support-ts-config-files/README.md | 117 +++++++++--------- 1 file changed, 60 insertions(+), 57 deletions(-) diff --git a/designs/2024-support-ts-config-files/README.md b/designs/2024-support-ts-config-files/README.md index 427c9529..2256f966 100644 --- a/designs/2024-support-ts-config-files/README.md +++ b/designs/2024-support-ts-config-files/README.md @@ -29,7 +29,7 @@ The primary motivation for adding support for TypeScript configuration files to used. Be sure to define any new terms in this section. --> -The goal is to seamlessly support TypeScript configuration files in ESLint. To achieve this, ESLint will need to recognize and parse TypeScript configuration files in the same way it does for JavaScript configuration files. This will involve creating the configuration file resolution logic to recognize `.ts`, `.mts`, and `.cts` files as valid ESLint configuration files. We will need to treat `eslint.config.mts` files as ECMAScript Modules (ESM) and `eslint.config.cts` files as CommonJS (CJS). The module resolution for `eslint.config.ts` files will depend on the `type` field in the nearest `package.json`. The maintainers of ESLint have raised some valid concerns some of which include: +The goal is to seamlessly support TypeScript configuration files in ESLint. To achieve this, ESLint will need to recognize and parse TypeScript configuration files in the same way it does for JavaScript configuration files. This will involve creating the configuration file resolution logic to recognize `.ts`, `.mts`, and `.cts` files as valid ESLint configuration files. We will need to treat these files as TypeScript files. The resolution of imported modules will depend on the `type` field in the nearest `package.json`, and users will be able to specify exports using either `module.exports` or the `export default` syntax regardless of the config file extension. The maintainers of ESLint have raised some valid concerns some of which include: - There should not be extra overhead for JavaScript users. This means this change should not have a significant impact (if any at all) affecting users who use plain JavaScript config files. - The external tools that are used to parse the config files written in TypeScript should not create side effects. Specifically, it is imperative that these tools do not interfere with Node.js's native module resolution system by hooking into or altering the standard `import/require` mechanisms. This means tools like [`ts-node`](https://github.com/TypeStrong/ts-node) and [`tsx`](https://github.com/privatenumber/tsx) might not be suitable for this purpose. @@ -47,9 +47,9 @@ inside [`lib/eslint/eslint.js`](https://github.com/eslint/eslint/blob/main/lib/e * @returns {boolean} `true` if the file is a TypeScript file, `false` if it's not. */ function isFileTS(filePath) { - const fileExtension = path.extname(filePath); + const fileExtension = path.extname(filePath) - return fileExtension.endsWith("ts"); + return fileExtension.endsWith("ts") } /** @@ -58,60 +58,63 @@ function isFileTS(filePath) { * @returns {Promise} The config loaded from the config file. */ async function loadFlatConfigFile(filePath) { - debug(`Loading config from ${filePath}`); - - const fileURL = pathToFileURL(filePath); - - debug(`Config file URL is ${fileURL}`); - - const mtime = (await fs.stat(filePath)).mtime.getTime(); - - /* - * Append a query with the config file's modification time (`mtime`) in order - * to import the current version of the config file. Without the query, `import()` would - * cache the config file module by the pathname only, and then always return - * the same version (the one that was actual when the module was imported for the first time). - * - * This ensures that the config file module is loaded and executed again - * if it has been changed since the last time it was imported. - * If it hasn't been changed, `import()` will just return the cached version. - * - * Note that we should not overuse queries (e.g., by appending the current time - * to always reload the config file module) as that could cause memory leaks - * because entries are never removed from the import cache. - */ - fileURL.searchParams.append("mtime", mtime); - - /* - * With queries, we can bypass the import cache. However, when import-ing a CJS module, - * Node.js uses the require infrastructure under the hood. That includes the require cache, - * which caches the config file module by its file path (queries have no effect). - * Therefore, we also need to clear the require cache before importing the config file module. - * In order to get the same behavior with ESM and CJS config files, in particular - to reload - * the config file only if it has been changed, we track file modification times and clear - * the require cache only if the file has been changed. - */ - if (importedConfigFileModificationTime.get(filePath) !== mtime) { - delete require.cache[filePath]; - } - - const isTS = isFileTS(filePath); - - if (isTS) { - const jiti = (await import("jiti")).default(__filename, { interopDefault: true, esmResolve: true }); - - const config = jiti(fileURL.href); - - importedConfigFileModificationTime.set(filePath, mtime); - - return config; - } - - const config = (await import(fileURL.href)).default; - - importedConfigFileModificationTime.set(filePath, mtime); - - return config; + debug(`Loading config from ${filePath}`) + + const fileURL = pathToFileURL(filePath) + + debug(`Config file URL is ${fileURL}`) + + const mtime = (await fs.stat(filePath)).mtime.getTime() + + /* + * Append a query with the config file's modification time (`mtime`) in order + * to import the current version of the config file. Without the query, `import()` would + * cache the config file module by the pathname only, and then always return + * the same version (the one that was actual when the module was imported for the first time). + * + * This ensures that the config file module is loaded and executed again + * if it has been changed since the last time it was imported. + * If it hasn't been changed, `import()` will just return the cached version. + * + * Note that we should not overuse queries (e.g., by appending the current time + * to always reload the config file module) as that could cause memory leaks + * because entries are never removed from the import cache. + */ + fileURL.searchParams.append("mtime", mtime) + + /* + * With queries, we can bypass the import cache. However, when import-ing a CJS module, + * Node.js uses the require infrastructure under the hood. That includes the require cache, + * which caches the config file module by its file path (queries have no effect). + * Therefore, we also need to clear the require cache before importing the config file module. + * In order to get the same behavior with ESM and CJS config files, in particular - to reload + * the config file only if it has been changed, we track file modification times and clear + * the require cache only if the file has been changed. + */ + if (importedConfigFileModificationTime.get(filePath) !== mtime) { + delete require.cache[filePath] + } + + const isTS = isFileTS(filePath) + + if (isTS) { + const jiti = (await import("jiti")).default(__filename, { + interopDefault: true, + esmResolve: true, + }) + + const config = jiti(fileURL.href) + + importedConfigFileModificationTime.set(filePath, mtime) + + return config + } + + const config = (await import(fileURL.href)).default + + importedConfigFileModificationTime.set(filePath, mtime) + + return config } ``` From fef64ec5e98c828616b279c033335de0cad0fb21 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Thu, 4 Apr 2024 17:58:35 -0500 Subject: [PATCH 20/23] Add disclaimer about `jiti` not supporting the top-level `await` syntax --- designs/2024-support-ts-config-files/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/designs/2024-support-ts-config-files/README.md b/designs/2024-support-ts-config-files/README.md index 2256f966..6e246ace 100644 --- a/designs/2024-support-ts-config-files/README.md +++ b/designs/2024-support-ts-config-files/README.md @@ -118,6 +118,9 @@ async function loadFlatConfigFile(filePath) { } ``` +> [!IMPORTANT] +> AS of now [`jiti`](https://www.npmjs.com/package/jiti) does not support the [top-level `await` syntax](https://github.com/unjs/jiti/issues/72) + ## Examples with `eslint.config.mts` file: From 6e8440f589439fca353922dacfa3db1f6161f723 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Tue, 14 May 2024 01:14:46 -0500 Subject: [PATCH 21/23] Remove section about `type` field in `package.json` --- designs/2024-support-ts-config-files/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2024-support-ts-config-files/README.md b/designs/2024-support-ts-config-files/README.md index 6e246ace..86804247 100644 --- a/designs/2024-support-ts-config-files/README.md +++ b/designs/2024-support-ts-config-files/README.md @@ -29,7 +29,7 @@ The primary motivation for adding support for TypeScript configuration files to used. Be sure to define any new terms in this section. --> -The goal is to seamlessly support TypeScript configuration files in ESLint. To achieve this, ESLint will need to recognize and parse TypeScript configuration files in the same way it does for JavaScript configuration files. This will involve creating the configuration file resolution logic to recognize `.ts`, `.mts`, and `.cts` files as valid ESLint configuration files. We will need to treat these files as TypeScript files. The resolution of imported modules will depend on the `type` field in the nearest `package.json`, and users will be able to specify exports using either `module.exports` or the `export default` syntax regardless of the config file extension. The maintainers of ESLint have raised some valid concerns some of which include: +The goal is to seamlessly support TypeScript configuration files in ESLint. To achieve this, ESLint will need to recognize and parse TypeScript configuration files in the same way it does for JavaScript configuration files. This will involve creating the configuration file resolution logic to recognize `.ts`, `.mts`, and `.cts` files as valid ESLint configuration files. We will need to treat these files as TypeScript files. Users will be able to specify exports using either `module.exports` or the `export default` syntax regardless of the config file extension. The maintainers of ESLint have raised some valid concerns some of which include: - There should not be extra overhead for JavaScript users. This means this change should not have a significant impact (if any at all) affecting users who use plain JavaScript config files. - The external tools that are used to parse the config files written in TypeScript should not create side effects. Specifically, it is imperative that these tools do not interfere with Node.js's native module resolution system by hooking into or altering the standard `import/require` mechanisms. This means tools like [`ts-node`](https://github.com/TypeStrong/ts-node) and [`tsx`](https://github.com/privatenumber/tsx) might not be suitable for this purpose. From 2a2c9f875a44428bcc50791a2f1268c301fc1fd7 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Tue, 14 May 2024 01:16:08 -0500 Subject: [PATCH 22/23] Slightly modify `Detailed Design` section --- designs/2024-support-ts-config-files/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2024-support-ts-config-files/README.md b/designs/2024-support-ts-config-files/README.md index 86804247..004289b5 100644 --- a/designs/2024-support-ts-config-files/README.md +++ b/designs/2024-support-ts-config-files/README.md @@ -32,7 +32,7 @@ The primary motivation for adding support for TypeScript configuration files to The goal is to seamlessly support TypeScript configuration files in ESLint. To achieve this, ESLint will need to recognize and parse TypeScript configuration files in the same way it does for JavaScript configuration files. This will involve creating the configuration file resolution logic to recognize `.ts`, `.mts`, and `.cts` files as valid ESLint configuration files. We will need to treat these files as TypeScript files. Users will be able to specify exports using either `module.exports` or the `export default` syntax regardless of the config file extension. The maintainers of ESLint have raised some valid concerns some of which include: - There should not be extra overhead for JavaScript users. This means this change should not have a significant impact (if any at all) affecting users who use plain JavaScript config files. -- The external tools that are used to parse the config files written in TypeScript should not create side effects. Specifically, it is imperative that these tools do not interfere with Node.js's native module resolution system by hooking into or altering the standard `import/require` mechanisms. This means tools like [`ts-node`](https://github.com/TypeStrong/ts-node) and [`tsx`](https://github.com/privatenumber/tsx) might not be suitable for this purpose. +- The external tools that are used to parse the config files written in TypeScript should not create side effects. Specifically, it is highly desirable that these tools do not interfere with Node.js's native module resolution system by hooking into or altering the standard `import/require` mechanisms. This means tools like [`ts-node`](https://github.com/TypeStrong/ts-node) and [`tsx`](https://github.com/privatenumber/tsx) might not be suitable for this purpose. So far the tool that seems to be the most suitable for this purpose is [`jiti`](https://www.npmjs.com/package/jiti). It does not introduce side effects and performs well, demonstrating its reliability. It also seems to be more battle-tested given some established frameworks such as [Nuxt](https://github.com/nuxt/nuxt), [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss) and [Docusaurus](https://github.com/facebook/docusaurus) have been using it to load their configuration files. From 263c467c292b8ffe42f55c8bc0d010ec18b60611 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Tue, 14 May 2024 01:17:17 -0500 Subject: [PATCH 23/23] Slightly modify `Summary` section --- designs/2024-support-ts-config-files/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2024-support-ts-config-files/README.md b/designs/2024-support-ts-config-files/README.md index 004289b5..a1aa5429 100644 --- a/designs/2024-support-ts-config-files/README.md +++ b/designs/2024-support-ts-config-files/README.md @@ -7,7 +7,7 @@ ## Summary -Add support for TypeScript config files (`eslint.config.ts`, `eslint.config.mts`, `eslint.config.cts`) +Add experimental support for TypeScript config files (`eslint.config.ts`, `eslint.config.mts`, `eslint.config.cts`)