From b1f1bd4a9835b9831320f08f1856c32347731843 Mon Sep 17 00:00:00 2001 From: Bijela Gora <102924398+bijela-gora@users.noreply.github.com> Date: Mon, 13 Mar 2023 19:35:14 +0200 Subject: [PATCH] Add support of 'extends' property in tsconfig.json (#33) * Move code related to reading tsconfig.json into a function * Make getCompilerOptions options support "extends" prop in tsconfig * Add a test for extends functionality * Simplify base tsconfig * Simplify tsconfig * Remove not important comment * Run prettier --- check.js | 12 +++---- create-program.js | 29 +++------------- get-compiler-options.js | 33 +++++++++++++++++++ package.json | 1 - pnpm-lock.yaml | 3 +- test/__snapshots__/check.test.js.snap | 7 ++++ test/check.test.js | 4 +++ .../tsconfig-with-extends/index.errors.ts | 6 ++++ .../tsconfig-with-extends/tsconfig.base.json | 5 +++ .../tsconfig-with-extends/tsconfig.json | 9 +++++ 10 files changed, 73 insertions(+), 36 deletions(-) create mode 100644 get-compiler-options.js create mode 100644 test/fixtures/tsconfig-with-extends/index.errors.ts create mode 100644 test/fixtures/tsconfig-with-extends/tsconfig.base.json create mode 100644 test/fixtures/tsconfig-with-extends/tsconfig.json diff --git a/check.js b/check.js index a836188..9bc9c68 100644 --- a/check.js +++ b/check.js @@ -1,5 +1,5 @@ import { dirname, basename, join, relative } from 'path' -import { existsSync, promises as fs } from 'fs' +import { promises as fs } from 'fs' import { createRequire } from 'module' import { fileURLToPath } from 'url' import { createSpinner } from 'nanospinner' @@ -7,7 +7,8 @@ import { location } from 'vfile-location' import { Worker } from 'worker_threads' import pico from 'picocolors' import glob from 'fast-glob' -import JSON5 from 'json5' + +import { getCompilerOptions } from './get-compiler-options.js' let require = createRequire(import.meta.url) @@ -74,12 +75,7 @@ export async function check( let spinner = createSpinner('Check types', { stream: stderr }) spinner.start() - let compilerOptions - let tsconfigPath = join(cwd, 'tsconfig.json') - if (existsSync(tsconfigPath)) { - let tsconfig = JSON5.parse(await fs.readFile(tsconfigPath)) - compilerOptions = tsconfig.compilerOptions - } + let compilerOptions = getCompilerOptions(cwd) let all = await glob(globs, { cwd, ignore: ['node_modules'], absolute: true }) diff --git a/create-program.js b/create-program.js index 96673f4..c71dfc5 100644 --- a/create-program.js +++ b/create-program.js @@ -6,34 +6,13 @@ let require = createRequire(import.meta.url) const TS_DIR = dirname(require.resolve('typescript')) -const DEFAULT = { - allowSyntheticDefaultImports: true, - strictFunctionTypes: false, - noUnusedParameters: true, - noImplicitReturns: true, - moduleResolution: 'NodeJs', - noUnusedLocals: true, - stripInternal: true, - allowJs: true, - module: 'esnext', - strict: true, - noEmit: true, - jsx: 'react' -} - -export function createProgram(files, opts = DEFAULT) { - opts.moduleResolution = 'node' - - let { options, errors } = ts.convertCompilerOptionsFromJson(opts, './') - - if (errors.length) { - throw errors - } +export function createProgram(files, options) { + options.moduleResolution = ts.ModuleResolutionKind.NodeJs if (options.lib) { - options.lib.forEach(path => { + for (let path of options.lib) { files.push(join(TS_DIR, path)) - }) + } options.lib.length = 0 } diff --git a/get-compiler-options.js b/get-compiler-options.js new file mode 100644 index 0000000..e46ecc4 --- /dev/null +++ b/get-compiler-options.js @@ -0,0 +1,33 @@ +import ts from 'typescript' + +const DEFAULT_OPTIONS = ts.convertCompilerOptionsFromJson( + { + allowSyntheticDefaultImports: true, + strictFunctionTypes: false, + noUnusedParameters: true, + noImplicitReturns: true, + moduleResolution: 'NodeJs', + noUnusedLocals: true, + stripInternal: true, + allowJs: true, + module: 'esnext', + strict: true, + noEmit: true, + jsx: 'react' + }, + './' +).options + +export function getCompilerOptions(cwd, configName = 'tsconfig.json') { + let configFileName = ts.findConfigFile(cwd, ts.sys.fileExists, configName) + if (configFileName === undefined) { + return DEFAULT_OPTIONS + } + let configFile = ts.readConfigFile(configFileName, ts.sys.readFile) + let parsedCommandLine = ts.parseJsonConfigFileContent( + configFile.config, + ts.sys, + cwd + ) + return parsedCommandLine.options +} diff --git a/package.json b/package.json index b3f860b..ff8fd79 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ }, "dependencies": { "fast-glob": "^3.2.12", - "json5": "^2.2.3", "nanospinner": "^1.1.0", "picocolors": "^1.0.0", "vfile-location": "^4.0.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a6eb350..2012cbd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,6 @@ specifiers: eslint-plugin-promise: ^6.1.1 fast-glob: ^3.2.12 jest: ^29.4.1 - json5: ^2.2.3 nanospinner: ^1.1.0 picocolors: ^1.0.0 print-snapshots: ^0.4.2 @@ -21,7 +20,6 @@ specifiers: dependencies: fast-glob: 3.2.12 - json5: 2.2.3 nanospinner: 1.1.0 picocolors: 1.0.0 vfile-location: 4.0.1 @@ -2648,6 +2646,7 @@ packages: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true + dev: true /kleur/3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} diff --git a/test/__snapshots__/check.test.js.snap b/test/__snapshots__/check.test.js.snap index d457bea..4a7cacb 100644 --- a/test/__snapshots__/check.test.js.snap +++ b/test/__snapshots__/check.test.js.snap @@ -91,6 +91,13 @@ exports[`loads custom tsconfig.json 1`] = ` " `; +exports[`loads custom tsconfig.json with extends property 1`] = ` +"- Check types +✔ Check types +✔ index.errors.ts +" +`; + exports[`supports simple cases 1`] = ` "- Check types ✔ Check types diff --git a/test/check.test.js b/test/check.test.js index 45f2546..2c97060 100644 --- a/test/check.test.js +++ b/test/check.test.js @@ -58,6 +58,10 @@ it('loads custom tsconfig.json', async () => { expect(await good('tsconfig')).toMatchSnapshot() }) +it('loads custom tsconfig.json with extends property', async () => { + expect(await good('tsconfig-with-extends')).toMatchSnapshot() +}) + it('accepts files', async () => { expect(await good('negative', 'b.*')).toMatchSnapshot() }) diff --git a/test/fixtures/tsconfig-with-extends/index.errors.ts b/test/fixtures/tsconfig-with-extends/index.errors.ts new file mode 100644 index 0000000..6ed9513 --- /dev/null +++ b/test/fixtures/tsconfig-with-extends/index.errors.ts @@ -0,0 +1,6 @@ +declare const a: number | null + +// THROWS Type 'number | null' is not assignable to type 'number'. +export const b: number = a + +// This file checks whether the compiler performs strictNullChecks if it is enabled in the config file specified via extends diff --git a/test/fixtures/tsconfig-with-extends/tsconfig.base.json b/test/fixtures/tsconfig-with-extends/tsconfig.base.json new file mode 100644 index 0000000..9647394 --- /dev/null +++ b/test/fixtures/tsconfig-with-extends/tsconfig.base.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "strictNullChecks": true + } +} diff --git a/test/fixtures/tsconfig-with-extends/tsconfig.json b/test/fixtures/tsconfig-with-extends/tsconfig.json new file mode 100644 index 0000000..f7ad1c7 --- /dev/null +++ b/test/fixtures/tsconfig-with-extends/tsconfig.json @@ -0,0 +1,9 @@ +{ + // tsconfig should support extends + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noUnusedLocals": false, + "target": "ES5", + "moduleResolution": "Node" + } +}