Skip to content

Commit c4a59c7

Browse files
authored
Merge pull request #1002 from hasparus/better-ts-errors
Friendlier TypeScript errors.
2 parents 406bad7 + 0756514 commit c4a59c7

File tree

15 files changed

+1191
-946
lines changed

15 files changed

+1191
-946
lines changed

package.json

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,38 +16,41 @@
1616
"packages/*"
1717
],
1818
"devDependencies": {
19-
"@babel/cli": "^7.4.4",
20-
"@babel/core": "^7.4.5",
21-
"@babel/helper-validator-identifier": "^7.9.0",
22-
"@babel/plugin-transform-runtime": "^7.7.6",
23-
"@babel/preset-env": "^7.4.5",
24-
"@babel/preset-react": "^7.0.0",
25-
"@babel/preset-typescript": "^7.8.3",
26-
"@babel/runtime": "^7.7.7",
19+
"@babel/cli": "^7.10.1",
20+
"@babel/core": "^7.10.2",
21+
"@babel/helper-validator-identifier": "^7.10.1",
22+
"@babel/plugin-transform-runtime": "^7.10.1",
23+
"@babel/preset-env": "^7.10.2",
24+
"@babel/preset-react": "^7.10.1",
25+
"@babel/preset-typescript": "^7.10.1",
26+
"@babel/runtime": "^7.10.2",
2727
"@testing-library/react": "^9.1.3",
2828
"@types/jest": "^25.1.2",
2929
"@types/lodash.debounce": "^4.0.6",
3030
"@types/lodash.merge": "^4.6.6",
3131
"@types/react-test-renderer": "^16.9.2",
32-
"babel-jest": "^25.3.0",
32+
"babel-jest": "^26.0.1",
3333
"husky": ">=4.0.7",
34-
"jest": "^24.8.0",
34+
"jest": "^26.0.1",
3535
"jest-canvas-mock": "^2.2.0",
36-
"jest-emotion": "^10.0.11",
37-
"jest-mock-console": "^1.0.0",
36+
"jest-emotion": "^10.0.32",
37+
"jest-mock-console": "^1.0.1",
3838
"lerna": "^3.14.1",
3939
"lint-staged": "10",
4040
"microbundle": "^0.11.0",
4141
"prettier": "^2.0.5",
4242
"react-test-renderer": "^16.8.6",
43-
"ts-jest": "^25.2.0"
43+
"ts-jest": "^26.1.0",
44+
"ts-snippet": "^4.2.0",
45+
"typescript": "^3.9.5"
4446
},
4547
"resolutions": {},
4648
"jest": {
4749
"preset": "ts-jest/presets/js-with-babel",
4850
"globals": {
4951
"ts-jest": {
5052
"tsConfig": {
53+
"target": "es2018",
5154
"module": "commonjs",
5255
"esModuleInterop": true,
5356
"resolveJsonModule": true,

packages/core/src/index.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ import {
33
ThemeContext as EmotionContext,
44
InterpolationWithTheme,
55
} from '@emotion/core'
6-
// @ts-ignore
76
import { css, Theme } from '@theme-ui/css'
8-
import React from 'react'
7+
import * as React from 'react'
98
import deepmerge from 'deepmerge'
109
import { version as __EMOTION_VERSION__ } from '@emotion/core/package.json'
1110

@@ -72,8 +71,14 @@ const arrayMerge = (destinationArray, sourceArray, options) => sourceArray
7271
export const merge = (a: Theme, b: Theme): Theme =>
7372
deepmerge(a, b, { isMergeableObject, arrayMerge })
7473

75-
merge.all = <T = Theme>(...args: Partial<T>[]) =>
76-
deepmerge.all<T>(args, { isMergeableObject, arrayMerge })
74+
function mergeAll<A, B>(a: A, B: B): A & B
75+
function mergeAll<A, B, C>(a: A, B: B, c: C): A & B & C
76+
function mergeAll<A, B, C, D>(a: A, B: B, c: C, d: D): A & B & C & D
77+
function mergeAll<T = Theme>(...args: Partial<T>[]) {
78+
return deepmerge.all<T>(args, { isMergeableObject, arrayMerge })
79+
}
80+
81+
merge.all = mergeAll
7782

7883
interface BaseProviderProps {
7984
context: ContextValue
@@ -109,7 +114,7 @@ export function ThemeProvider({ theme, children }: ThemeProviderProps) {
109114
const context =
110115
typeof theme === 'function'
111116
? { ...outer, theme: theme(outer.theme) }
112-
: merge.all<ContextValue>({}, outer, { theme })
117+
: merge.all({}, outer, { theme })
113118

114119
return jsx(BaseProvider, { context }, children)
115120
}
File renamed without changes.
Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,20 @@ import renderer from 'react-test-renderer'
44
import { render, fireEvent, cleanup, act } from '@testing-library/react'
55
import { matchers } from 'jest-emotion'
66
import mockConsole from 'jest-mock-console'
7-
import { jsx, Context, useThemeUI, merge, ThemeProvider } from '../src'
7+
import {
8+
jsx,
9+
Context,
10+
useThemeUI,
11+
merge,
12+
ThemeProvider,
13+
ContextValue,
14+
} from '../src'
815

916
afterEach(cleanup)
1017

1118
expect.extend(matchers)
1219

13-
const renderJSON = (el) => renderer.create(el).toJSON()
20+
const renderJSON = (el: React.ReactElement) => renderer.create(el).toJSON()
1421

1522
describe('ThemeProvider', () => {
1623
test('renders', () => {
@@ -27,7 +34,8 @@ describe('ThemeProvider', () => {
2734
const json = renderJSON(
2835
<Context.Provider
2936
value={{
30-
emotionVersion: '9.0.0',
37+
__EMOTION_VERSION__: '9.0.0',
38+
theme: {},
3139
}}>
3240
<ThemeProvider theme={{}}>Conflicting versions</ThemeProvider>
3341
</Context.Provider>
@@ -216,7 +224,7 @@ describe('jsx', () => {
216224
test('does not add css prop when not provided', () => {
217225
jest.spyOn(global.console, 'warn')
218226
const json = renderJSON(jsx(React.Fragment, null, 'hi'))
219-
expect(json.props).toEqual(undefined)
227+
expect(json?.props).toEqual(undefined)
220228
expect(console.warn).not.toBeCalled()
221229
})
222230
})
@@ -225,6 +233,7 @@ describe('merge', () => {
225233
test('deeply merges objects', () => {
226234
const result = merge(
227235
{
236+
// @ts-ignore
228237
beep: 'boop',
229238
hello: {
230239
hi: 'howdy',
@@ -267,9 +276,12 @@ describe('merge', () => {
267276
})
268277

269278
test('does not attempt to merge React components', () => {
270-
const h1 = React.forwardRef((props, ref) => <h1 ref={ref} {...props} />)
279+
const h1 = React.forwardRef<HTMLHeadingElement, {}>((props, ref) => (
280+
<h1 ref={ref} {...props} />
281+
))
271282
const result = merge(
272283
{
284+
//@ts-ignore
273285
h1: (props) => <h1 {...props} />,
274286
},
275287
{
@@ -282,53 +294,53 @@ describe('merge', () => {
282294
test('primitive types override arrays', () => {
283295
const result = merge(
284296
{
285-
fontSize: [3, 4, 5],
297+
fontSizes: [3, 4, 5],
286298
},
287299
{
288-
fontSize: 4,
300+
fontSizes: 4 as any,
289301
}
290302
)
291303
expect(result).toEqual({
292-
fontSize: 4,
304+
fontSizes: 4,
293305
})
294306
})
295307

296308
test('arrays override arrays', () => {
297309
const result = merge(
298310
{
299-
fontSize: [3, 4, 5],
311+
fontSizes: [3, 4, 5],
300312
},
301313
{
302-
fontSize: [6, 7],
314+
fontSizes: [6, 7],
303315
}
304316
)
305317
expect(result).toEqual({
306-
fontSize: [6, 7],
318+
fontSizes: [6, 7],
307319
})
308320
})
309321

310322
test('arrays override primitive types', () => {
311323
const result = merge(
312324
{
313-
fontSize: 5,
325+
fontSizes: 5 as any,
314326
},
315327
{
316-
fontSize: [6, 7],
328+
fontSizes: [6, 7],
317329
}
318330
)
319331
expect(result).toEqual({
320-
fontSize: [6, 7],
332+
fontSizes: [6, 7],
321333
})
322334
})
323335
})
324336

325337
// describe('Context', () => {})
326338
describe('useThemeUI', () => {
327339
test('returns theme context', () => {
328-
let context
329-
const GetContext = (props) => {
340+
let context: ContextValue | undefined
341+
const GetContext = () => {
330342
context = useThemeUI()
331-
return false
343+
return null
332344
}
333345
renderJSON(
334346
<ThemeProvider
@@ -341,6 +353,6 @@ describe('useThemeUI', () => {
341353
</ThemeProvider>
342354
)
343355
expect(context).toBeTruthy()
344-
expect(context.theme.colors.text).toBe('tomato')
356+
expect(context?.theme.colors?.text).toBe('tomato')
345357
})
346358
})

packages/css/src/index.ts

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
1-
import { CSSObject, ThemeUIStyleObject, UseThemeFunction, Theme } from './types'
1+
import {
2+
CSSObject,
3+
ThemeUIStyleObject,
4+
ThemeDerivedStyles,
5+
Theme,
6+
ThemeUICSSObject,
7+
} from './types'
28

39
export * from './types'
410

511
export function get(
612
obj: object,
7-
key: string | number,
13+
key: string | number | undefined,
814
def?: unknown,
915
p?: number,
1016
undef?: unknown
1117
): any {
1218
const path = key && typeof key === 'string' ? key.split('.') : [key]
1319
for (p = 0; p < path.length; p++) {
14-
obj = obj ? (obj as any)[path[p]] : undef
20+
obj = obj ? (obj as any)[path[p]!] : undef
1521
}
1622
return obj === undef ? def : obj
1723
}
1824

19-
export const defaultBreakpoints = [40, 52, 64].map(n => n + 'em')
25+
export const defaultBreakpoints = [40, 52, 64].map((n) => n + 'em')
2026

2127
const defaultTheme = {
2228
space: [0, 4, 8, 16, 32, 64, 128, 256, 512],
@@ -215,20 +221,23 @@ const transforms = [
215221
{}
216222
)
217223

218-
const responsive = (styles: Exclude<ThemeUIStyleObject, UseThemeFunction>) => (
219-
theme?: Theme
220-
) => {
221-
const next: Exclude<ThemeUIStyleObject, UseThemeFunction> = {}
224+
const responsive = (
225+
styles: Exclude<ThemeUIStyleObject, ThemeDerivedStyles>
226+
) => (theme?: Theme) => {
227+
const next: Exclude<ThemeUIStyleObject, ThemeDerivedStyles> = {}
222228
const breakpoints =
223229
(theme && (theme.breakpoints as string[])) || defaultBreakpoints
224230
const mediaQueries = [
225231
null,
226-
...breakpoints.map(n => `@media screen and (min-width: ${n})`),
232+
...breakpoints.map((n) => `@media screen and (min-width: ${n})`),
227233
]
228234

229-
for (const key in styles) {
230-
const value =
231-
typeof styles[key] === 'function' ? styles[key](theme) : styles[key]
235+
for (const k in styles) {
236+
const key = k as keyof typeof styles
237+
let value = styles[key]
238+
if (typeof value === 'function') {
239+
value = value(theme || {})
240+
}
232241

233242
if (value == null) continue
234243
if (!Array.isArray(value)) {
@@ -243,7 +252,7 @@ const responsive = (styles: Exclude<ThemeUIStyleObject, UseThemeFunction>) => (
243252
}
244253
next[media] = next[media] || {}
245254
if (value[i] == null) continue
246-
next[media][key] = value[i]
255+
;(next[media] as Record<string, any>)[key] = value[i]
247256
}
248257
}
249258

@@ -259,22 +268,23 @@ export const css = (args: ThemeUIStyleObject = {}) => (
259268
...defaultTheme,
260269
...('theme' in props ? props.theme : props),
261270
}
262-
let result = {}
271+
let result: CSSObject = {}
263272
const obj = typeof args === 'function' ? args(theme) : args
264273
const styles = responsive(obj)(theme)
265274

266275
for (const key in styles) {
267-
const x = styles[key]
276+
const x = styles[key as keyof typeof styles]
268277
const val = typeof x === 'function' ? x(theme) : x
269278

270279
if (key === 'variant') {
271-
const variant = css(get(theme, val))(theme)
280+
const variant = css(get(theme, val as string))(theme)
272281
result = { ...result, ...variant }
273282
continue
274283
}
275284

276285
if (val && typeof val === 'object') {
277-
result[key] = css(val)(theme)
286+
// TODO: val can also be an array here. Is this a bug? Can it be reproduced?
287+
result[key] = css(val as ThemeUICSSObject)(theme)
278288
continue
279289
}
280290

@@ -284,8 +294,8 @@ export const css = (args: ThemeUIStyleObject = {}) => (
284294
const transform: any = get(transforms, prop, get)
285295
const value = transform(scale, val, val)
286296

287-
if (multiples[prop]) {
288-
const dirs = multiples[prop]
297+
if (prop in multiples) {
298+
const dirs = multiples[prop as keyof typeof multiples]
289299

290300
for (let i = 0; i < dirs.length; i++) {
291301
result[dirs[i]] = value

0 commit comments

Comments
 (0)