Skip to content

fix: improve tsconfig extends checks #61413

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 30 additions & 8 deletions packages/next/src/lib/typescript/writeConfigurationDefaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,33 @@ export async function writeConfigurationDefaults(
)
)
} else if (hasAppDir && !rawConfig.include.includes(nextAppTypes)) {
userTsConfig.include.push(nextAppTypes)
suggestedActions.push(
cyan('include') + ' was updated to add ' + bold(`'${nextAppTypes}'`)
)
if (!Array.isArray(userTsConfig.include)) {
userTsConfig.include = []
}
// rawConfig will resolve all extends and include paths (ex: tsconfig.json, tsconfig.base.json, etc.)
// if it doesn't match userTsConfig then update the userTsConfig to add the
// rawConfig's includes in addition to nextAppTypes
if (
rawConfig.include.length !== userTsConfig.include.length ||
JSON.stringify(rawConfig.include.sort()) !==
JSON.stringify(userTsConfig.include.sort())
) {
userTsConfig.include.push(...rawConfig.include, nextAppTypes)
suggestedActions.push(
cyan('include') +
' was set to ' +
bold(
`[${[...rawConfig.include, nextAppTypes]
.map((i) => `'${i}'`)
.join(', ')}]`
)
)
} else {
userTsConfig.include.push(nextAppTypes)
suggestedActions.push(
cyan('include') + ' was updated to add ' + bold(`'${nextAppTypes}'`)
)
}
}

// Enable the Next.js typescript plugin.
Expand Down Expand Up @@ -270,14 +293,13 @@ export async function writeConfigurationDefaults(
)
}

// If `strict` is set to `false` or `strictNullChecks` is set to `false`,
// If `strict` is set to `false` and `strictNullChecks` is set to `false`,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated this comment to reflect the checks

// then set `strictNullChecks` to `true`.
if (
hasPagesDir &&
hasAppDir &&
userTsConfig.compilerOptions &&
!userTsConfig.compilerOptions.strict &&
!('strictNullChecks' in userTsConfig.compilerOptions)
!tsOptions.strict &&
!('strictNullChecks' in tsOptions)
Comment on lines +301 to +302
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

favoring tsOptions which includes the configs from the extends target

) {
userTsConfig.compilerOptions.strictNullChecks = true
suggestedActions.push(
Expand Down
6 changes: 2 additions & 4 deletions test/integration/tsconfig-verifier/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -695,8 +695,7 @@ import path from 'path'
"{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"target": "ES2017",
"strictNullChecks": true
"target": "ES2017"
}
}
"
Expand Down Expand Up @@ -764,8 +763,7 @@ import path from 'path'
"extends": "./tsconfig.base.json",
"compilerOptions": {
"target": "ES2017",
"incremental": true,
"strictNullChecks": true
"incremental": true
}
}
"
Expand Down
102 changes: 102 additions & 0 deletions test/unit/write-configuration-defaults.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/* eslint-env jest */
import fs from 'fs-extra'
import { join } from 'path'
import { writeConfigurationDefaults } from 'next/dist/lib/typescript/writeConfigurationDefaults'
import * as ts from 'typescript'

const fixtureDir = join(__dirname, 'fixtures/config-ts')
const tsconfigFile = join(fixtureDir, 'tsconfig.json')
const tsconfigBaseFile = join(fixtureDir, 'tsconfig.base.json')
const distDir = '.next'
const nextAppTypes = `${distDir}/types/**/*.ts`

describe('tsconfig.base.json', () => {
beforeEach(async () => {
await fs.ensureDir(fixtureDir)
})
afterEach(async () => {
await fs.remove(tsconfigFile)
await fs.remove(tsconfigBaseFile)
})

describe('appDir', () => {
it('should support empty includes when base provides it', async () => {
const include = ['**/*.ts', '**/*.tsx', nextAppTypes]
const content = {
extends: './tsconfig.base.json',
}
const baseContent = {
include,
}

await fs.writeFile(tsconfigFile, JSON.stringify(content, null, 2))
await fs.writeFile(tsconfigBaseFile, JSON.stringify(baseContent, null, 2))

await expect(
writeConfigurationDefaults(ts, tsconfigFile, false, true, distDir, true)
).resolves.not.toThrow()

const output = await fs.readFile(tsconfigFile, 'utf8')
const parsed = JSON.parse(output)

expect(parsed.include).toBeUndefined()
})

it('should replace includes when base is missing appTypes', async () => {
const include = ['**/*.ts', '**/*.tsx']
const content = {
extends: './tsconfig.base.json',
}
const baseContent = {
include,
}

await fs.writeFile(tsconfigFile, JSON.stringify(content, null, 2))
await fs.writeFile(tsconfigBaseFile, JSON.stringify(baseContent, null, 2))

await expect(
writeConfigurationDefaults(ts, tsconfigFile, false, true, distDir, true)
).resolves.not.toThrow()

const output = await fs.readFile(tsconfigFile, 'utf8')
const parsed = JSON.parse(output)

expect(parsed.include.sort()).toMatchInlineSnapshot(`
[
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
]
`)
})

it('should not add strictNullChecks if base provides it', async () => {
const content = {
extends: './tsconfig.base.json',
}

const baseContent = {
compilerOptions: {
strictNullChecks: true,
strict: true,
},
}

await fs.writeFile(tsconfigFile, JSON.stringify(content, null, 2))
await fs.writeFile(tsconfigBaseFile, JSON.stringify(baseContent, null, 2))

await writeConfigurationDefaults(
ts,
tsconfigFile,
false,
true,
distDir,
true
)
const output = await fs.readFile(tsconfigFile, 'utf8')
const parsed = JSON.parse(output)

expect(parsed.compilerOptions.strictNullChecks).toBeUndefined()
})
})
})