Skip to content

Commit cd11b42

Browse files
kylorhall-atlassianJakeLane
andauthoredDec 7, 2023
Block invalid properties on the strict API cssMap when extended type was met. (#1582)
* Block invalid properties on `cssMap` when extended type was met. Previously, `asdf` in this example would never error because part of the interface existed, so this was "extended", eg. `{ color: …, asdf: … }` extends `{ color: … }` was true. So instead of using the inferred generic `TStyles extends …` to enforce, we enforce the type explicitly, and use the inferred generic `TStyles` to build our output type. This maintains the proper output type while blocking the input type. More easily visualized in the playground: - Before: [TS Playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBDAnmApnAVHAhgZzgYQGVC4AzKCEOAcgGMcclVqBuAKDZQA9JYFk0RQgAUcKAK4ATCHgC8bOHAA+NAGQAudVlIwUUaguVrNAIyy0A1pIpgDildQ3qTKUtBR2jjzbXEfDDk6+KAC0UCgA5sAQAHaegZqkwFCMIQA2KDC6+gHG6kkpMOnAMf72eRFQWCAgWFAhehQ55d7qtVAWevF5YGnmKAAWEGmSXbmtYhm0MNFx4044qGlpJRENUE3drTB1EZkhulwwW04AbsAoAO4HVTE4wDOxJ1rTwKdlXhpY4jAQScvPEx9GIWZ60AYoSwoSTPUakb5pY7zdRwkrQ2HAHBYIHo5EocBIZ4oGLYjIw5EFRjPNy+HDUiC0kLne44+mMy4PAYlaniZY4WjhYnPIbvZqfdQlMJYGJ7Z4lUbZEAlLC6OUxU5YFbkloaDI6Z4rEEGhma4pG5EQMCPElpZ4QH4hP5SmUfBJgb5ibXisDAabicIhSU+v3hZ69fpDEZ6EI4IaXOY69ThxCrZ7hLCSR0xNKINMoDMhS5QB6u4zhACO4mSuMTxYiAyRiZ2UD2jfF4jE9RKGq1zw70Z7wC9CUHw+MzJLMPYHG4vHgTEExGENj0MxQcjgACV89Js4gADxCAB0y8tq4uOH3jGLMqMMXEIBcUAAfM-pwuCJQfWTCOD8Vg4FkOBCBgYtpiEU9UFgC84FUOBRAkaQcAAEUhPoqmtHBp1naB5wEYDQN9GAIJXaD10Arcd1iHND2II8QOlSQ6kkSDz3IuDj0IU4IlYsicDfDgPwQqQZFQ2h0JVWYNwAb0MABtABFOASgIJcxBEnAAF0AH51AIsDiKXUi1ywtgAF933wgBRGI3CgWgUF-CFan3AAVAB5EwACtIRgZ8KNkxQ5OEZSYjgTpED+OAPO83ydL0kLuF0GJJDwCKovwL9gB-P9akMRRtOizyfOmYLNLgJLiVSyjaGgSQr0ImUABo4HEEEYggeNn3yxQ4EKmy7Icpz-zc4rfLK7ret6vSYpKmAyp6vTSlFdgLME-DMvAbLoV4pAxIk61t1IPRiQcgL5OoAB9S78HcgBZYQAEkABkrOQy7hE3dzhCszdXIATUu5CrPwZ6AEFNzB1zHvcgA5S7NysgAxX6rNh-ArKB9zLth9zXMugB1TdHtczHkMexH8Fc57Acu6hNL00C-FW7CeFw-hUE-LafyQDJL1m3z-KAwK4EU0LwpQSLSCK2LpgZmW5sU8rKpSvBt1qqB6uvVYWraiwOq6nrCs279oRAxA+dG2X5oUzTJumrnTZY4zEH2upJNiI6TpiByWY4Wq7ngAV810Ci3OG2oKqOKq8BN7bJAjrBnwACgASkA-yRcUUg2teWI4HoHA7qwMA4H3Hqptc82+aj5LqvVuqGpvCIWpAgySLPPjYPg9SkLdjCpO7gboCG3KsHDsfXx6lOK96xgLfXGbq-XHrU70rOprgdNdxzUWQpU9Lpar3n13luOeYX-nl5wCb2Cmsy4A3qaAHpn7gAABGAcAaHhfPWJo4AhBCHAS4AwVQoFFLPRQ4QYD+jCvPPmd9epmQ4FNGBcDH4FwYMXUuKCUH+1iIwTBhccFwAfkBYO4D9wb1qmkaAelqAaigMnIBqUDhJVTtQJqPVvAii6OvAuwx6E0CYSwkIbDDhFD4VAThZDzIp1TtOAORCEEoFIRQ7BJdk4bw6qUdeUDBF0KgAw0RrDv6SM4dwzevVeEQFFNQARtDhGMLqGIiRSUQjSNkWZKx1jjAdRgMnfIyRUjgmypIdQASgmUiKGEkYqdOGOKEcYmg4QYRkN8dYxwcB-ImB+L8OISSjEMKBH4agcj77mVTkAA) - After: [TS Playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBDAnmApnAVHAhgZzgYQGVC4AzKCEOAcgGMcclVqBuAKDZQA9JYFk0RQgAUcKAK4ATCHgC8bOHAA+NAGQAudVlIwUUaguVrNAIyy0A1pIpgDildQ3qTKUtBR2jjzbXEfDDk6+KAC0UCgA5sAQAHaegZqkwFCMIQA2KDC6+gHG6kkpMOnAMf72eRFQWCAgWFAhehQ55d7qtVAWevF5YGnmKAAWEGmSXbmtYhm0MNFx4044qGlpJRENUE3drTB1EZkhulwwW04AbsAoAO4HVTE4wDOxJ1rTwKdlXhpY4jAQScvPEx9GIWZ60AYoSwoSTPUakb5pY7zdRwkrQ2HAHBYIHo5EocBIZ4oGLYjIw5EFRjPNy+HDUiC0kLne44+mMy4PAYlaniZY4WjhYnPIbvZqfdQlMJYGJ7Z4lUbZEAlLC6OUxU5YFbkloaDI6Z4rEEGhma4pG5EQMCPElpZ4QH4hP5SmUfBJgb5ibXisDAabicIhSU+v3hZ69fpDEZ6EI4IaXOY69ThxCrZ7hLCSR0xNKINMoDMhS5QB6u4zhACO4mSuMTxYiAyRiZ2UD2jfF4jE9RKGq1zw70Z7wC9CUHw+MzJLMPYHG4vHgTEExGENj0MxQcjgACV89Js4gADxCAB0y8tq4uOH3jGLMqMMXEIBcUAAfM-pwuCJQfWTCOD8Vg4FkOBCBgYtpiEU9UFgC84FUOBRAkaQcAAEUhPoqmtHBp1naB5wEYDQN9GAIJXaD10Arcd1iHND2II8QOlSQ6kkSDz3IuDj0IU4IlYsicDfDgPwQqQZFQ2h0JVWYNwAb0MABtABFOASgIJcxBEnAAF0AH51AIsDiKXUi1ywtgAF933wgBRGI3CgWgUF-CFan3AAVAB5EwACtIRgZ8KNkxQ5OEZSYjgTpED+OAPO83ydL0kLuF0GJJDwCKovwL9gB-P9akMRRtOizyfOmYLNLgJLiVSyjaGgSQr0ImUABo4HEEEYggeNn3yxQ4EKmy7Icpz-zc4rfLK7ret6vSYpKmAyp6vTSlFdgLME-DMvAbLoV4pAxIk61t1IPRiQcgL5OoAB9S78HcgBZYQAEkABkrOQy7hE3dzhCszdXIATUu5CrPwZ6AEFNzB1zHvcgA5S7NysgAxX6rNh-ArKB9zLth9zXMugB1TdHtczHkMexH8Fc57Acu6hNL00C-FW7CeFw-hUE-LafyQDJL1m3z-KAwK4EU0LwpQSLSCK2LpgZmW5sU8rKpSvBt1qqB6uvVYWraiwOq6nrCs279oRAxA+dG2X5oUzTJumrnTZY4zEH2upJNiI6TpiByWY4Wq7ngAV810Ci3OG2oKqOKq8BN7bJAjrBnwACgASkA-yRcUUg2teWI4HoHA7qwMA4H3Hqptc82+aj5LqvVuqGpvCIWpAgySLPPjYPg9SkLdjCpO7gboCG3KsHDsfXx6lOK96xgLfXPSG81pudf0oiO6gkzu+Evu0PdzCh9skfHLHw8spy5yk-txRU70rOprgdNdxzUWQpU9Lpar3n13luOeYXvzau64JrsCmmZOAD8poAHpoFwAAAIwBwA0Hgvl1hNDgCEEIcBLgDBVCgUUs9FDhBgP6MK88+ZgN6mZDgU0SFkMgQXBgxdS40Jof7WIjBGGFxYXACBQFg74P3A-WqaRoB6WoBqKAycsGpQOElVO1Amo9W8CKLo98C7DHETQKRMiQhyMOEUNRUBFF8PMinVO04A5cIoSgXhAjmEl2Tg-DqpR75EM0WIqAEjdGyOQYYxRyjH69VURAUU1ANGiO0ZIuoeiDFJRCMY0xZkgnBOMB1GAyd8jJFSOCbKkh1AZKyZSIoeSRip0UZErR3iaDhBhHw1JwTHBwH8iYH4vw4hVK8RIoEfhqBmPAeZVOQA) * Fix strictly scoped test and extend to the `css()` call as well. - The `expect(getByTestId('div')).toHaveCompiledCss('bkgrnd', 'red');` was wrong. - Adds this to `css()` calls to ensure we don't regress in the future. * Minimize repetition of `TSchema` styles and update generic on `cssMap` to be distinct. * Split our "invalid css property" test into multiple call sites to test different variants. --------- Co-authored-by: Jake Lane <jlane2@atlassian.com>
1 parent 3bb89ef commit cd11b42

File tree

3 files changed

+133
-12
lines changed

3 files changed

+133
-12
lines changed
 

‎.changeset/rich-toys-care.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@compiled/react': patch
3+
---
4+
5+
Block invalid properties on our `cssMap` input objects to avoid invalid css and other mistakes.

‎packages/react/src/create-strict-api/__tests__/index.test.tsx

+116
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,56 @@ describe('createStrictAPI()', () => {
8686

8787
expect(getByTestId('div')).toHaveCompiledCss('all', 'inherit');
8888
});
89+
90+
it('should type error with css properties not in the style scope', () => {
91+
// NOTE: These are split into mutliple `css()` calls to ensure the type errors are not hidden
92+
// as only one will error at a time when combined into one query
93+
94+
const bgStyles = css({
95+
fontWeight: 'bold', // just a valid property to ensure the `extends` keyword isn't working as intended
96+
// @ts-expect-error - Object literal may only specify known properties, and 'bg' does not exist in type …
97+
bg: 'red',
98+
});
99+
100+
const colourStyles = css({
101+
fontWeight: 'bold', // just a valid property to ensure the `extends` keyword isn't working as intended
102+
// @ts-expect-error - Object literal may only specify known properties, and 'colour' does not exist in type …
103+
colour: 'var(--ds-text)',
104+
});
105+
106+
const hoverStyles = css({
107+
fontWeight: 'bold', // just a valid property to ensure the `extends` keyword isn't working as intended
108+
'&:hover': {
109+
fontWeight: 'bold', // just a valid property to ensure the `extends` keyword isn't working as intended
110+
// @ts-expect-error - Object literal may only specify known properties, and 'colour' does not exist in type …
111+
colour: 'var(--ds-text-hover)',
112+
},
113+
});
114+
115+
const invalidPsuedoStyles = css({
116+
fontWeight: 'bold', // just a valid property to ensure the `extends` keyword isn't working as intended
117+
// @ts-expect-error - bject literal may only specify known properties, and ''&:invalid-pseudo'' does not exist in type …
118+
'&:invalid-pseudo': {
119+
color: 'var(--ds-text)',
120+
},
121+
});
122+
123+
const { getByTestId } = render(
124+
<div css={[bgStyles, colourStyles, hoverStyles, invalidPsuedoStyles]} data-testid="div" />
125+
);
126+
127+
expect(getByTestId('div')).toHaveCompiledCss('font-weight', 'bold');
128+
expect(getByTestId('div')).toHaveCompiledCss('font-weight', 'bold', {
129+
target: ':hover',
130+
});
131+
132+
// These still get compiled to css, even if they're not valid
133+
expect(getByTestId('div')).toHaveCompiledCss('bg', 'red');
134+
expect(getByTestId('div')).toHaveCompiledCss('colour', 'var(--ds-text)');
135+
expect(getByTestId('div')).toHaveCompiledCss('colour', 'var(--ds-text-hover)', {
136+
target: ':hover',
137+
});
138+
});
89139
});
90140

91141
describe('cssMap()', () => {
@@ -152,6 +202,72 @@ describe('createStrictAPI()', () => {
152202

153203
expect(getByTestId('div')).toHaveCompiledCss('val', 'ok', { target: ':hover' });
154204
});
205+
206+
it('should type error with css properties not in the style scope', () => {
207+
// NOTE: These are split into mutliple `css()` calls to ensure the type errors are not hidden
208+
// as only one will error at a time when combined into one query
209+
210+
const bgStyles = cssMap({
211+
primary: {
212+
fontWeight: 'bold', // just a valid property to ensure the `extends` keyword isn't working as intended
213+
// @ts-expect-error - Object literal may only specify known properties, and 'bg' does not exist in type …
214+
bg: 'red',
215+
},
216+
});
217+
218+
const colourStyles = cssMap({
219+
primary: {
220+
fontWeight: 'bold', // just a valid property to ensure the `extends` keyword isn't working as intended
221+
// @ts-expect-error - Object literal may only specify known properties, and 'colour' does not exist in type …
222+
colour: 'var(--ds-text)',
223+
},
224+
});
225+
226+
const hoverStyles = cssMap({
227+
primary: {
228+
fontWeight: 'bold', // just a valid property to ensure the `extends` keyword isn't working as intended
229+
'&:hover': {
230+
fontWeight: 'bold', // just a valid property to ensure the `extends` keyword isn't working as intended
231+
// @ts-expect-error - Object literal may only specify known properties, and 'colour' does not exist in type …
232+
colour: 'var(--ds-text-hover)',
233+
},
234+
},
235+
});
236+
237+
const invalidPsuedoStyles = cssMap({
238+
primary: {
239+
fontWeight: 'bold', // just a valid property to ensure the `extends` keyword isn't working as intended
240+
// @ts-expect-error - bject literal may only specify known properties, and ''&:invalid-pseudo'' does not exist in type …
241+
'&:invalid-pseudo': {
242+
color: 'var(--ds-text)',
243+
},
244+
},
245+
});
246+
247+
const { getByTestId } = render(
248+
<div
249+
css={[
250+
bgStyles.primary,
251+
colourStyles.primary,
252+
hoverStyles.primary,
253+
invalidPsuedoStyles.primary,
254+
]}
255+
data-testid="div"
256+
/>
257+
);
258+
259+
expect(getByTestId('div')).toHaveCompiledCss('font-weight', 'bold');
260+
expect(getByTestId('div')).toHaveCompiledCss('font-weight', 'bold', {
261+
target: ':hover',
262+
});
263+
264+
// These still get compiled to css, even if they're not valid
265+
expect(getByTestId('div')).toHaveCompiledCss('bg', 'red');
266+
expect(getByTestId('div')).toHaveCompiledCss('colour', 'var(--ds-text)');
267+
expect(getByTestId('div')).toHaveCompiledCss('colour', 'var(--ds-text-hover)', {
268+
target: ':hover',
269+
});
270+
});
155271
});
156272

157273
describe('XCSSProp', () => {

‎packages/react/src/create-strict-api/index.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ type PickObjects<TObject> = {
1818
[P in keyof TObject]: TObject[P] extends Record<string, unknown> ? TObject[P] : never;
1919
};
2020

21-
interface CompiledAPI<TSchema> {
21+
type CSSStyles<TSchema extends CompiledSchema> = StrictCSSProperties &
22+
PseudosDeclarations &
23+
EnforceSchema<TSchema>;
24+
type CSSMapStyles<TSchema extends CompiledSchema> = Record<string, CSSStyles<TSchema>>;
25+
26+
interface CompiledAPI<TSchema extends CompiledSchema> {
2227
/**
2328
* ## CSS
2429
*
@@ -34,9 +39,7 @@ interface CompiledAPI<TSchema> {
3439
* <div css={redText} />
3540
* ```
3641
*/
37-
css(
38-
styles: StrictCSSProperties & PseudosDeclarations & EnforceSchema<TSchema>
39-
): StrictCSSProperties;
42+
css(styles: CSSStyles<TSchema>): StrictCSSProperties;
4043
/**
4144
* ## CSS Map
4245
*
@@ -53,15 +56,12 @@ interface CompiledAPI<TSchema> {
5356
* <div css={styles.solid} />
5457
* ```
5558
*/
56-
cssMap<
57-
TStyles extends Record<
58-
string,
59-
StrictCSSProperties & PseudosDeclarations & EnforceSchema<TSchema>
60-
>
61-
>(
62-
styles: TStyles
59+
cssMap<TStylesMap extends CSSMapStyles<TSchema>>(
60+
// NOTE: This should match the generic `TStylesMap extends …` as we want this arg to strictly satisfy this type, not just extend it.
61+
// The "extends" functionality is to infer and build the return type, this is to enforce the input type.
62+
styles: CSSMapStyles<TSchema>
6363
): {
64-
readonly [P in keyof TStyles]: CompiledStyles<TStyles[P]>;
64+
readonly [P in keyof TStylesMap]: CompiledStyles<TStylesMap[P]>;
6565
};
6666
/**
6767
* ## CX

0 commit comments

Comments
 (0)
Please sign in to comment.