Skip to content

Commit

Permalink
fix(@formatjs/intl): fix type checker errors with TypeScript 4.9 (#3916)
Browse files Browse the repository at this point in the history
Supersedes #3912
Fixes #3905
Fixes #3910

This PR fixes the type checking issue of `MessageIds` and `Locale` under TypeScript 4.9.

It may be a TypeScript bug, because this does not work:

```ts
type MessageIds = FormatjsIntl.Message extends {ids: string}
  ? FormatjsIntl.Message['ids']
  : string
```

But this works:

```ts
type _Message = FormatjsIntl.Message
type MessageIds = _Message extends {ids: string}
  ? _Message['ids']
  : string
```

🤷 The only difference is aliasing the type from the namespace.

---

I also added a `tsd` test to verify that the global type override works as expected.
  • Loading branch information
pyrocat101 authored Nov 23, 2022
1 parent 9336c9a commit 36d0437
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 7 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
"ts-jest": "28",
"ts-loader": "^9.1.2",
"ts-node": "^10.8",
"tsd": "^0.24.1",
"tslib": "2.4.0",
"typescript": "^4.7",
"unidiff": "^1.0.2",
Expand Down
30 changes: 30 additions & 0 deletions packages/intl/BUILD
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
load("@aspect_bazel_lib//lib:write_source_files.bzl", "write_source_files")
load("@aspect_rules_js//npm/private:npm_package.bzl", "npm_package")
load("@npm//:tsd/package_json.bzl", tsd_bin = "bin")
load("@npm//:defs.bzl", "npm_link_all_packages")
load("//tools:index.bzl", "check_format", "package_json_test", "ts_compile")
load("//tools:jest.bzl", "jest_test")
load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")

npm_link_all_packages(name = "node_modules")

Expand Down Expand Up @@ -77,3 +79,31 @@ package_json_test(
name = "package_json_test",
deps = SRC_DEPS,
)

# TODO: make this a macro that paramtrizes on `.test-d.ts` files.
copy_to_directory(
name = "global_type_overrides_test_files",
srcs = [
":%s" % PACKAGE_NAME,
"tests/global_type_overrides.test-d.ts"
],
out = "type_test_files",
# Remove the prefix so the package directory merges with the test file directory.
replace_prefixes = dict([
("%s" % PACKAGE_NAME, "")
])
)

tsd_bin.tsd_test(
name = "global_type_overrides_test",
data = [
"//:node_modules/tsd",
":global_type_overrides_test_files",
"tsconfig.json",
] + SRC_DEPS,
args = [
"--files",
"tests/**/*.test-d.ts"
],
chdir = "packages/%s/type_test_files" % PACKAGE_NAME
)
15 changes: 10 additions & 5 deletions packages/intl/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import {
import {DEFAULT_INTL_CONFIG} from './utils'
import {NumberFormatOptions} from '@formatjs/ecma402-abstract'

// Note: FormatjsIntl is defined as a global namespace so the library user can
// override the default types of Message.ids (e.g. as string literal unions from extracted strings)
// or IntlConfig.locale (e.g. to a list of supported locales).
declare global {
namespace FormatjsIntl {
interface Message {}
Expand All @@ -31,12 +34,14 @@ declare global {
}
}

type MessageIds = FormatjsIntl.Message extends {ids: string}
? FormatjsIntl.Message['ids']
: string
// NOTE: workaround the TypeScript 4.9 bug: https://github.com/formatjs/formatjs/issues/3910
type _Message = FormatjsIntl.Message
type MessageIds = _Message extends {ids: string} ? _Message['ids'] : string

type Locale = FormatjsIntl.IntlConfig extends {locale: string}
? FormatjsIntl.IntlConfig['locale']
// NOTE: workaround the TypeScript 4.9 bug: https://github.com/formatjs/formatjs/issues/3910
type _IntlConfig = FormatjsIntl.IntlConfig
type Locale = _IntlConfig extends {locale: string}
? _IntlConfig['locale']
: string

export type OnErrorFn = (
Expand Down
18 changes: 18 additions & 0 deletions packages/intl/tests/global_type_overrides.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {expectType} from 'tsd'
import {MessageDescriptor, ResolvedIntlConfig} from '../src/types'

// Example type overrides
declare global {
namespace FormatjsIntl {
interface Message {
ids: 'a' | 'b'
}
interface IntlConfig {
locale: 'en-US' | 'zh-CN'
}
}
}

// Check that the type overrides actually work.
expectType<'a' | 'b'>(null as any as NonNullable<MessageDescriptor['id']>)
expectType<'en-US' | 'zh-CN'>(null as any as ResolvedIntlConfig['locale'])
74 changes: 74 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
"jsx": "react"
},
"exclude": [
"packages/react-intl/example-sandboxes"
"packages/react-intl/example-sandboxes",
// Exclude any tsd tests because they can mess with normal
// type checking in editors.
"**/*.test-d.ts"
]
}
}

0 comments on commit 36d0437

Please sign in to comment.