fix: enable typescript strictNullChecks

This commit is contained in:
Luma 2021-08-23 23:46:46 +09:00 committed by Christian Kaisermann
parent 2537314af5
commit bf4189a862
16 changed files with 73 additions and 42 deletions

View File

@ -23,7 +23,7 @@ const FORMAT_METHOD_NAMES = new Set(['format', '_', 't']);
function isFormatCall(node: Node, imports: Set<string>) { function isFormatCall(node: Node, imports: Set<string>) {
if (node.type !== 'CallExpression') return false; if (node.type !== 'CallExpression') return false;
let identifier: Identifier; let identifier: Identifier | undefined;
if (node.callee.type === 'Identifier') { if (node.callee.type === 'Identifier') {
identifier = node.callee; identifier = node.callee;
@ -125,7 +125,9 @@ export function collectMessageDefinitions(ast: Ast) {
definitionDict.properties.map((propNode) => { definitionDict.properties.map((propNode) => {
if (propNode.type !== 'Property') { if (propNode.type !== 'Property') {
throw new Error( throw new Error(
`Found invalid '${propNode.type}' at L${propNode.loc.start.line}:${propNode.loc.start.column}`, `Found invalid '${propNode.type}' at L${propNode.loc!.start.line}:${
propNode.loc!.start.column
}`,
); );
} }
@ -143,7 +145,7 @@ export function collectMessages(markup: string): Message[] {
...definitions.map((definition) => getObjFromExpression(definition)), ...definitions.map((definition) => getObjFromExpression(definition)),
...calls.map((call) => { ...calls.map((call) => {
const [pathNode, options] = call.arguments; const [pathNode, options] = call.arguments;
let messageObj; let messageObj: Partial<Message>;
if (pathNode.type === 'ObjectExpression') { if (pathNode.type === 'ObjectExpression') {
// _({ ...opts }) // _({ ...opts })
@ -166,7 +168,7 @@ export function collectMessages(markup: string): Message[] {
return messageObj; return messageObj;
}), }),
].filter(Boolean); ].filter((Boolean as unknown) as (x: Message | null) => x is Message);
} }
export function extractMessages( export function extractMessages(

View File

@ -2,8 +2,10 @@ import type { ObjectExpression, Property, Identifier } from 'estree';
import type { Message } from '../types'; import type { Message } from '../types';
export function getObjFromExpression(exprNode: ObjectExpression) { export function getObjFromExpression(
return exprNode.properties.reduce<Message>((acc, prop: Property) => { exprNode: ObjectExpression,
): Partial<Message> {
return exprNode.properties.reduce<Partial<Message>>((acc, prop: Property) => {
// we only want primitives // we only want primitives
if ( if (
prop.value.type === 'Literal' && prop.value.type === 'Literal' &&

View File

@ -1,5 +1,5 @@
export interface Message { export interface Message {
id?: string; id: string;
default?: string; default: string;
[key: string]: any; [key: string]: any;
} }

View File

@ -1,4 +1,4 @@
import type { ConfigureOptions } from './types'; import type { ConfigureOptions, ConfigureOptionsInit } from './types';
import { $locale } from './stores/locale'; import { $locale } from './stores/locale';
interface Formats { interface Formats {
@ -47,13 +47,13 @@ export const defaultOptions: ConfigureOptions = {
ignoreTag: true, ignoreTag: true,
}; };
const options: ConfigureOptions = defaultOptions; const options: ConfigureOptions = defaultOptions as any;
export function getOptions() { export function getOptions() {
return options; return options;
} }
export function init(opts: ConfigureOptions) { export function init(opts: ConfigureOptionsInit) {
const { formats, ...rest } = opts; const { formats, ...rest } = opts;
const initialLocale = opts.initialLocale || opts.fallbackLocale; const initialLocale = opts.initialLocale || opts.fallbackLocale;

View File

@ -1,6 +1,9 @@
import IntlMessageFormat from 'intl-messageformat'; import IntlMessageFormat from 'intl-messageformat';
import type { MemoizedIntlFormatter } from '../types'; import type {
MemoizedIntlFormatter,
MemoizedIntlFormatterOptional,
} from '../types';
import { getCurrentLocale } from '../stores/locale'; import { getCurrentLocale } from '../stores/locale';
import { getOptions } from '../configs'; import { getOptions } from '../configs';
import { monadicMemoize } from './memoize'; import { monadicMemoize } from './memoize';
@ -15,6 +18,16 @@ type MemoizedDateTimeFormatterFactory = MemoizedIntlFormatter<
Intl.DateTimeFormatOptions Intl.DateTimeFormatOptions
>; >;
type MemoizedNumberFormatterFactoryOptional = MemoizedIntlFormatterOptional<
Intl.NumberFormat,
Intl.NumberFormatOptions
>;
type MemoizedDateTimeFormatterFactoryOptional = MemoizedIntlFormatterOptional<
Intl.DateTimeFormat,
Intl.DateTimeFormatOptions
>;
const getIntlFormatterOptions = ( const getIntlFormatterOptions = (
type: 'time' | 'number' | 'date', type: 'time' | 'number' | 'date',
name: string, name: string,
@ -76,17 +89,17 @@ const createTimeFormatter: MemoizedDateTimeFormatterFactory = monadicMemoize(
}, },
); );
export const getNumberFormatter: MemoizedNumberFormatterFactory = ({ export const getNumberFormatter: MemoizedNumberFormatterFactoryOptional = ({
locale = getCurrentLocale(), locale = getCurrentLocale(),
...args ...args
} = {}) => createNumberFormatter({ locale, ...args }); } = {}) => createNumberFormatter({ locale, ...args });
export const getDateFormatter: MemoizedDateTimeFormatterFactory = ({ export const getDateFormatter: MemoizedDateTimeFormatterFactoryOptional = ({
locale = getCurrentLocale(), locale = getCurrentLocale(),
...args ...args
} = {}) => createDateFormatter({ locale, ...args }); } = {}) => createDateFormatter({ locale, ...args });
export const getTimeFormatter: MemoizedDateTimeFormatterFactory = ({ export const getTimeFormatter: MemoizedDateTimeFormatterFactoryOptional = ({
locale = getCurrentLocale(), locale = getCurrentLocale(),
...args ...args
} = {}) => createTimeFormatter({ locale, ...args }); } = {}) => createTimeFormatter({ locale, ...args });

View File

@ -62,7 +62,7 @@ function loadLocaleQueue(locale: string, localeQueue: MessagesLoader[]) {
const activeFlushes: { [key: string]: Promise<void> } = {}; const activeFlushes: { [key: string]: Promise<void> } = {};
export function flush(locale: string): Promise<void> { export async function flush(locale: string): Promise<void> {
if (!hasLocaleQueue(locale)) { if (!hasLocaleQueue(locale)) {
if (locale in activeFlushes) { if (locale in activeFlushes) {
return activeFlushes[locale]; return activeFlushes[locale];

View File

@ -15,7 +15,7 @@ const addToCache = (path: string, locale: string, message: string) => {
return message; return message;
}; };
export const lookup = (path: string, refLocale: string) => { export const lookup = (path: string, refLocale: string | null | undefined) => {
if (refLocale == null) return undefined; if (refLocale == null) return undefined;
if (refLocale in lookupCache && path in lookupCache[refLocale]) { if (refLocale in lookupCache && path in lookupCache[refLocale]) {

View File

@ -31,7 +31,7 @@ export function defineMessages(i: Record<string, MessageObject>) {
} }
export function waitLocale(locale?: string) { export function waitLocale(locale?: string) {
return flush(locale || getCurrentLocale() || getOptions().initialLocale); return flush(locale || getCurrentLocale() || getOptions().initialLocale!);
} }
export { export {

View File

@ -34,7 +34,9 @@ export function getMessageFromDictionary(locale: string, id: string) {
return match; return match;
} }
export function getClosestAvailableLocale(refLocale: string): string | null { export function getClosestAvailableLocale(
refLocale: string,
): string | null | undefined {
if (refLocale == null) return undefined; if (refLocale == null) return undefined;
const relatedLocales = getPossibleLocales(refLocale); const relatedLocales = getPossibleLocales(refLocale);

View File

@ -23,7 +23,7 @@ import { getCurrentLocale, getPossibleLocales, $locale } from './locale';
const formatMessage: MessageFormatter = (id, options = {}) => { const formatMessage: MessageFormatter = (id, options = {}) => {
if (typeof id === 'object') { if (typeof id === 'object') {
options = id as MessageObject; options = id as MessageObject;
id = options.id; id = options.id!;
} }
const { const {
@ -90,11 +90,8 @@ const formatNumber: NumberFormatter = (n, options) => {
return getNumberFormatter(options).format(n); return getNumberFormatter(options).format(n);
}; };
const getJSON: JSONGetter = <T = any>( const getJSON: JSONGetter = (id: string, locale = getCurrentLocale()): any => {
id: string, return lookup(id, locale);
locale = getCurrentLocale(),
) => {
return lookup(id, locale) as T;
}; };
export const $format = derived([$locale, $dictionary], () => formatMessage); export const $format = derived([$locale, $dictionary], () => formatMessage);

View File

@ -6,7 +6,7 @@ import { getClosestAvailableLocale } from './dictionary';
import { $isLoading } from './loading'; import { $isLoading } from './loading';
let current: string; let current: string;
const $locale = writable(null); const $locale = writable<string | null | undefined>(null);
function getSubLocales(refLocale: string) { function getSubLocales(refLocale: string) {
return refLocale return refLocale
@ -77,7 +77,8 @@ $locale.set = (newLocale: string): void | Promise<void> => {
}; };
// istanbul ignore next // istanbul ignore next
$locale.update = (fn: (locale: string) => void | Promise<void>) => $locale.update = (
localeSet(fn(current)); fn: (value: string | null | undefined) => string | null | undefined,
) => localeSet(fn(current));
export { $locale }; export { $locale };

View File

@ -1,7 +1,11 @@
import type { FormatXMLElementFn, Formats } from 'intl-messageformat'; import type { FormatXMLElementFn, Formats } from 'intl-messageformat';
export interface LocaleDictionary { export interface LocaleDictionary {
[key: string]: LocaleDictionary | string | Array<string | LocaleDictionary>; [key: string]:
| LocaleDictionary
| string
| Array<string | LocaleDictionary>
| null;
} }
export type LocalesDictionary = { export type LocalesDictionary = {
@ -49,7 +53,7 @@ export type NumberFormatter = (
options?: IntlFormatterOptions<Intl.NumberFormatOptions>, options?: IntlFormatterOptions<Intl.NumberFormatOptions>,
) => string; ) => string;
export type JSONGetter = <T>(id: string, locale?: string) => T; export type JSONGetter = (id: string, locale?: string) => any;
type IntlFormatterOptions<T> = T & { type IntlFormatterOptions<T> = T & {
format?: string; format?: string;
@ -57,6 +61,10 @@ type IntlFormatterOptions<T> = T & {
}; };
export interface MemoizedIntlFormatter<T, U> { export interface MemoizedIntlFormatter<T, U> {
(options: IntlFormatterOptions<U>): T;
}
export interface MemoizedIntlFormatterOptional<T, U> {
(options?: IntlFormatterOptions<U>): T; (options?: IntlFormatterOptions<U>): T;
} }
@ -65,10 +73,14 @@ export interface MessagesLoader {
} }
export interface ConfigureOptions { export interface ConfigureOptions {
fallbackLocale: string; fallbackLocale: string | null | undefined;
formats?: Partial<Formats>; formats: Formats;
initialLocale?: string; initialLocale: string | null;
loadingDelay?: number; loadingDelay: number;
warnOnMissingMessages?: boolean; warnOnMissingMessages: boolean;
ignoreTag?: boolean; ignoreTag: boolean;
} }
export type ConfigureOptionsInit = Pick<ConfigureOptions, 'fallbackLocale'> &
Partial<Record<'formats', Partial<ConfigureOptions['formats']>>> &
Partial<Omit<ConfigureOptions, 'fallbackLocale' | 'formats'>>;

View File

@ -23,8 +23,8 @@ test('checks if exist queues of locale and its fallbacks', () => {
expect(hasLocaleQueue('en-US')).toBe(true); expect(hasLocaleQueue('en-US')).toBe(true);
}); });
test("does nothing if there's no queue for a locale", () => { test("does nothing if there's no queue for a locale", async () => {
expect(flush('foo')).toBeUndefined(); expect(await flush('foo')).toBeUndefined();
}); });
test('flushes the queue of a locale and its fallbacks and merge the result with the dictionary', async () => { test('flushes the queue of a locale and its fallbacks and merge the result with the dictionary', async () => {

View File

@ -8,7 +8,7 @@ import {
describe('getting client locale', () => { describe('getting client locale', () => {
beforeEach(() => { beforeEach(() => {
delete window.location; delete (window as any).location;
window.location = { window.location = {
pathname: '/', pathname: '/',
hostname: 'example.com', hostname: 'example.com',

View File

@ -1,10 +1,12 @@
{ {
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"strictNullChecks": true,
"noImplicitAny": true, "noImplicitAny": true,
"sourceMap": false, "sourceMap": false,
"module": "esnext", "module": "esnext",
"moduleResolution": "node", "moduleResolution": "node",
"skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"target": "es2017", "target": "es2017",

View File

@ -1263,9 +1263,9 @@
integrity sha512-lg55ArB+ZiHHbBBttLpzD07akz0QPrZgUODNakeC09i62dnrywr9mFErHuaPlB6I7z+sEbK+IYmplahvplCj2g== integrity sha512-lg55ArB+ZiHHbBBttLpzD07akz0QPrZgUODNakeC09i62dnrywr9mFErHuaPlB6I7z+sEbK+IYmplahvplCj2g==
"@types/node@^14.14.35": "@types/node@^14.14.35":
version "14.14.35" version "14.17.11"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.35.tgz#42c953a4e2b18ab931f72477e7012172f4ffa313" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.11.tgz#82d266d657aec5ff01ca59f2ffaff1bb43f7bf0f"
integrity sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag== integrity sha512-n2OQ+0Bz6WEsUjrvcHD1xZ8K+Kgo4cn9/w94s1bJS690QMUWfJPW/m7CCb7gPkA1fcYwL2UpjXP/rq/Eo41m6w==
"@types/normalize-package-data@^2.4.0": "@types/normalize-package-data@^2.4.0":
version "2.4.0" version "2.4.0"