From a815db35d84b99709ac1f1de9d016d3b7d11e84a Mon Sep 17 00:00:00 2001 From: Spencer Snyder Date: Tue, 4 Apr 2023 23:33:08 -0400 Subject: [PATCH] Support TypeScript 5 (#712) --- .gitignore | 2 + lib/options-manager.js | 69 +++---------------- package.json | 4 +- .../@sindresorhus/tsconfig/package.json | 26 +++++++ .../@sindresorhus/tsconfig/tsconfig.json | 35 ++++++++++ .../@tsconfig/node16/package.json | 1 + .../@tsconfig/node16/tsconfig.json | 16 +++++ .../typescript/extends-array/package.json | 3 + .../typescript/extends-array/tsconfig.json | 6 ++ test/options-manager.js | 9 +++ 10 files changed, 111 insertions(+), 60 deletions(-) create mode 100644 test/fixtures/typescript/extends-array/node_modules/@sindresorhus/tsconfig/package.json create mode 100644 test/fixtures/typescript/extends-array/node_modules/@sindresorhus/tsconfig/tsconfig.json create mode 100644 test/fixtures/typescript/extends-array/node_modules/@tsconfig/node16/package.json create mode 100644 test/fixtures/typescript/extends-array/node_modules/@tsconfig/node16/tsconfig.json create mode 100644 test/fixtures/typescript/extends-array/package.json create mode 100644 test/fixtures/typescript/extends-array/tsconfig.json diff --git a/.gitignore b/.gitignore index b4881e87..ee3e1c15 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,7 @@ test/fixtures/project/node_modules/.cache test/fixtures/typescript/extends-module/node_modules/.cache !test/fixtures/typescript/extends-tsconfig-bases/node_modules test/fixtures/typescript/extends-tsconfig-bases/node_modules/.cache +!test/fixtures/typescript/extends-array/node_modules +test/fixtures/typescript/extends-array/node_modules/.cache .nyc_output coverage diff --git a/lib/options-manager.js b/lib/options-manager.js index 874ec04f..52e5e4ff 100644 --- a/lib/options-manager.js +++ b/lib/options-manager.js @@ -2,7 +2,6 @@ import {existsSync, promises as fs} from 'node:fs'; import process from 'node:process'; import os from 'node:os'; import path from 'node:path'; -import {createRequire} from 'node:module'; import arrify from 'arrify'; import {mergeWith, flow, pick} from 'lodash-es'; import {findUpSync} from 'find-up'; @@ -11,12 +10,12 @@ import prettier from 'prettier'; import semver from 'semver'; import {cosmiconfig, defaultLoaders} from 'cosmiconfig'; import micromatch from 'micromatch'; -import JSON5 from 'json5'; import stringify from 'json-stable-stringify-without-jsonify'; import {Legacy} from '@eslint/eslintrc'; import createEsmUtils from 'esm-utils'; import MurmurHash3 from 'imurmurhash'; import slash from 'slash'; +import {getTsconfig} from 'get-tsconfig'; import { DEFAULT_IGNORES, DEFAULT_EXTENSION, @@ -159,26 +158,20 @@ const handleTSConfig = async options => { options.tsConfig = {}; options.tsConfigPath = ''; - const {project: tsConfigProjectPath, tsconfigRootDir} = options.parserOptions || {}; + const {project: tsConfigProjectPath} = options.parserOptions || {}; if (tsConfigProjectPath) { options.tsConfigPath = path.resolve(options.cwd, tsConfigProjectPath); - options.tsConfig = JSON5.parse(await fs.readFile(options.tsConfigPath)); + options.tsConfig = tsConfigResolvePaths(getTsconfig(options.tsConfigPath).config, options.tsConfigPath); } else { - const tsConfigExplorer = cosmiconfig([], { - searchPlaces: ['tsconfig.json'], - loaders: {'.json': (_, content) => JSON5.parse(content)}, - stopDir: tsconfigRootDir, - }); - const searchResults = (await tsConfigExplorer.search(options.filePath)) || {}; - options.tsConfigPath = searchResults.filepath; - options.tsConfig = searchResults.config; - } - - if (options.tsConfig) { - // If the tsconfig extends from another file, we need to ensure that the file is covered by the tsconfig - // or not. The basefile could have includes/excludes/files properties that should be applied to the final tsconfig representation. - options.tsConfig = await recursiveBuildTsConfig(options.tsConfig, options.tsConfigPath); + const {config: tsConfig, path: filepath} = getTsconfig(options.filePath) || {}; + options.tsConfigPath = filepath; + options.tsConfig = tsConfig; + if (options.tsConfigPath) { + options.tsConfig = tsConfigResolvePaths(tsConfig, options.tsConfigPath); + } else { + delete options.tsConfig; + } } let hasMatch; @@ -637,46 +630,6 @@ const getOptionGroups = async (files, options) => { return optionGroups; }; -async function recursiveBuildTsConfig(tsConfig, tsConfigPath) { - tsConfig = tsConfigResolvePaths(tsConfig, tsConfigPath); - - if (!tsConfig.extends || (typeof tsConfig.extends === 'string' && tsConfig.extends.includes('node_modules'))) { - return tsConfig; - } - - // If any of the following are missing, then we need to look up the base config as it could apply - const require = createRequire(tsConfigPath); - - let basePath; - try { - basePath = require.resolve(tsConfig.extends); - } catch (error) { - // Tsconfig resolution is odd, It allows behavior that is not exactly like node resolution - // therefore we attempt to smooth this out here with this hack - try { - basePath = require.resolve(path.join(tsConfig.extends, 'tsconfig.json')); - } catch { - // Throw the orginal resolution error to let the user know their extends block is invalid - throw error; - } - } - - const baseTsConfig = JSON5.parse(await fs.readFile(basePath)); - - delete tsConfig.extends; - - tsConfig = { - compilerOptions: { - ...baseTsConfig.compilerOptions, - ...tsConfig.compilerOptions, - }, - ...baseTsConfig, - ...tsConfig, - }; - - return recursiveBuildTsConfig(tsConfig, basePath); -} - // Convert all include, files, and exclude to absolute paths // and or globs. This works because ts only allows simple glob subset const tsConfigResolvePaths = (tsConfig, tsConfigPath) => { diff --git a/package.json b/package.json index eff43df6..a47e1895 100644 --- a/package.json +++ b/package.json @@ -81,10 +81,10 @@ "find-cache-dir": "^4.0.0", "find-up": "^6.3.0", "get-stdin": "^9.0.0", + "get-tsconfig": "^4.5.0", "globby": "^13.1.2", "imurmurhash": "^0.1.4", "json-stable-stringify-without-jsonify": "^1.0.1", - "json5": "^2.2.1", "lodash-es": "^4.17.21", "meow": "^11.0.0", "micromatch": "^4.0.5", @@ -93,7 +93,7 @@ "semver": "^7.3.8", "slash": "^5.0.0", "to-absolute-glob": "^2.0.2", - "typescript": "^4.9.3" + "typescript": "^5.0.3" }, "devDependencies": { "ava": "^5.1.0", diff --git a/test/fixtures/typescript/extends-array/node_modules/@sindresorhus/tsconfig/package.json b/test/fixtures/typescript/extends-array/node_modules/@sindresorhus/tsconfig/package.json new file mode 100644 index 00000000..918d2924 --- /dev/null +++ b/test/fixtures/typescript/extends-array/node_modules/@sindresorhus/tsconfig/package.json @@ -0,0 +1,26 @@ +{ + "name": "@sindresorhus/tsconfig", + "version": "3.0.1", + "description": "Shared TypeScript config for my projects", + "license": "MIT", + "repository": "sindresorhus/tsconfig", + "author": { + "name": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "https://sindresorhus.com" + }, + "main": "tsconfig.json", + "engines": { + "node": ">=14" + }, + "files": [ + "tsconfig.json" + ], + "keywords": [ + "tsconfig", + "typescript", + "ts", + "config", + "configuration" + ] +} diff --git a/test/fixtures/typescript/extends-array/node_modules/@sindresorhus/tsconfig/tsconfig.json b/test/fixtures/typescript/extends-array/node_modules/@sindresorhus/tsconfig/tsconfig.json new file mode 100644 index 00000000..0612e961 --- /dev/null +++ b/test/fixtures/typescript/extends-array/node_modules/@sindresorhus/tsconfig/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + // Disabled because of https://github.com/Microsoft/TypeScript/issues/29172 + // "outDir": "dist", + + "module": "node16", + "moduleResolution": "node16", + "moduleDetection": "force", + "target": "ES2020", // Node.js 14 + "lib": [ + "DOM", + "DOM.Iterable", + "ES2020" + ], + "allowSyntheticDefaultImports": true, // To provide backwards compatibility, Node.js allows you to import most CommonJS packages with a default import. This flag tells TypeScript that it's okay to use import on CommonJS modules. + "resolveJsonModule": false, // ESM doesn't yet support JSON modules. + "jsx": "react", + "declaration": true, + "pretty": true, + "newLine": "lf", + "stripInternal": true, + "strict": true, + "noImplicitReturns": true, + "noImplicitOverride": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noPropertyAccessFromIndexSignature": true, + "noEmitOnError": true, + "useDefineForClassFields": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true + } +} diff --git a/test/fixtures/typescript/extends-array/node_modules/@tsconfig/node16/package.json b/test/fixtures/typescript/extends-array/node_modules/@tsconfig/node16/package.json new file mode 100644 index 00000000..c0e6dfa8 --- /dev/null +++ b/test/fixtures/typescript/extends-array/node_modules/@tsconfig/node16/package.json @@ -0,0 +1 @@ +{"name":"@tsconfig/node16","repository":{"type":"git","url":"https://github.com/tsconfig/bases.git","directory":"bases"},"license":"MIT","description":"A base TSConfig for working with Node 16.","keywords":["tsconfig","node16"],"version":"1.0.3"} \ No newline at end of file diff --git a/test/fixtures/typescript/extends-array/node_modules/@tsconfig/node16/tsconfig.json b/test/fixtures/typescript/extends-array/node_modules/@tsconfig/node16/tsconfig.json new file mode 100644 index 00000000..262ff50b --- /dev/null +++ b/test/fixtures/typescript/extends-array/node_modules/@tsconfig/node16/tsconfig.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Node 16", + + "compilerOptions": { + "lib": ["es2021"], + "module": "commonjs", + "target": "es2021", + + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node" + } +} diff --git a/test/fixtures/typescript/extends-array/package.json b/test/fixtures/typescript/extends-array/package.json new file mode 100644 index 00000000..90bd27c7 --- /dev/null +++ b/test/fixtures/typescript/extends-array/package.json @@ -0,0 +1,3 @@ +{ + "xo": {} +} diff --git a/test/fixtures/typescript/extends-array/tsconfig.json b/test/fixtures/typescript/extends-array/tsconfig.json new file mode 100644 index 00000000..a0ad755a --- /dev/null +++ b/test/fixtures/typescript/extends-array/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": [ + "@sindresorhus/tsconfig", + "@tsconfig/node16" + ], +} diff --git a/test/options-manager.js b/test/options-manager.js index 057d78c8..3910bc5c 100644 --- a/test/options-manager.js +++ b/test/options-manager.js @@ -691,6 +691,15 @@ test('mergeWithFileConfig: tsconfig can properly extend tsconfig base node_modul t.is(options.tsConfigPath, expectedConfigPath); }); +test('mergeWithFileConfig: tsconfig can properly resolve extends arrays introduced in ts 5', async t => { + const cwd = path.resolve('fixtures', 'typescript', 'extends-array'); + const expectedConfigPath = path.join(cwd, 'tsconfig.json'); + const filePath = path.resolve(cwd, 'does-not-matter.ts'); + await t.notThrowsAsync(manager.mergeWithFileConfig({cwd, filePath})); + const {options} = await manager.mergeWithFileConfig({cwd, filePath}); + t.is(options.tsConfigPath, expectedConfigPath); +}); + test('applyOverrides', t => { t.deepEqual( manager.applyOverrides(