diff --git a/packages/cache/src/index.js b/packages/cache/src/index.js index 2157aa1bdd..5d7b3d1f7a 100644 --- a/packages/cache/src/index.js +++ b/packages/cache/src/index.js @@ -201,21 +201,30 @@ let createCache = (options?: Options): EmotionCache => { stylis.use((context, content, selectors) => { switch (context) { - case 2: { - for (let i = 0, len = selectors.length; len > i; i++) { - // :last-child isn't included here since it's safe - // because a style element will never be the last element - let match = selectors[i].match(/:(first|nth|nth-last)-child/) - if (match !== null) { - console.error( - `The pseudo class "${ - match[0] - }" is potentially unsafe when doing server-side rendering. Try changing it to "${ - match[1] - }-of-type"` + case -1: { + const flag = + 'emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason' + const unsafePseudoClasses = content.match( + /(:first|:nth|:nth-last)-child/g + ) + + if (unsafePseudoClasses) { + unsafePseudoClasses.forEach(unsafePseudoClass => { + const ignoreRegExp = new RegExp( + `${unsafePseudoClass}(\\(\\d\\))? \\/\\* ${flag} \\*\\/` ) - } + const ignore = ignoreRegExp.test(content) + + if (unsafePseudoClass && !ignore) { + console.error( + `The pseudo class "${unsafePseudoClass}" is potentially unsafe when doing server-side rendering. Try changing it to "${ + unsafePseudoClass.split('-child')[0] + }-of-type".` + ) + } + }) } + break } } diff --git a/packages/core/__tests__/__snapshots__/warnings.js.snap b/packages/core/__tests__/__snapshots__/warnings.js.snap index ea2df3fb52..894e5a8ef0 100644 --- a/packages/core/__tests__/__snapshots__/warnings.js.snap +++ b/packages/core/__tests__/__snapshots__/warnings.js.snap @@ -40,7 +40,381 @@ exports[`does warn when invalid values are passed for the content property 2`] = /> `; -exports[`does warn when using :first-child 1`] = ` +exports[`global with css prop 1`] = `null`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child /* [flag] */ :nth-child(3) /* [flag] */" in a style object 1`] = ` +.emotion-0:first-child :nth-child(3) { + color: rebeccapurple; +} + +
+`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child /* [flag] */ :nth-child(3) /* [flag] */" in a style string 1`] = ` +.emotion-0:first-child :nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child /* [flag] */ span" in a style object 1`] = ` +.emotion-0:first-child span { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child /* [flag] */ span" in a style string 1`] = ` +.emotion-0:first-child span { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child /* [flag] */ span:nth-child(3) /* [flag] */" in a style object 1`] = ` +.emotion-0:first-child span:nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child /* [flag] */ span:nth-child(3) /* [flag] */" in a style string 1`] = ` +.emotion-0:first-child span:nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child /* [flag] */" in a style object 1`] = ` +.emotion-0:first-child { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child /* [flag] */" in a style string 1`] = ` +.emotion-0:first-child { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child /* [flag] */, :nth-child(3) /* [flag] */" in a style object 1`] = ` +.emotion-0:first-child, +.emotion-0:nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child /* [flag] */, :nth-child(3) /* [flag] */" in a style string 1`] = ` +.emotion-0:first-child, +.emotion-0:nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child /* [flag] */, span" in a style object 1`] = ` +.emotion-0:first-child, +.emotion-0 span { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child /* [flag] */, span" in a style string 1`] = ` +.emotion-0:first-child, +.emotion-0 span { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child /* [flag] */, span:nth-child(3) /* [flag] */" in a style object 1`] = ` +.emotion-0:first-child, +.emotion-0 span:nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child /* [flag] */, span:nth-child(3) /* [flag] */" in a style string 1`] = ` +.emotion-0:first-child, +.emotion-0 span:nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child /* [flag] */:nth-child(3) /* [flag] */" in a style object 1`] = ` +.emotion-0:first-child :nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child /* [flag] */:nth-child(3) /* [flag] */" in a style string 1`] = ` +.emotion-0:first-child :nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":nth-child(3) /* [flag] */" in a style object 1`] = ` +.emotion-0:nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":nth-child(3) /* [flag] */" in a style string 1`] = ` +.emotion-0:nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":nth-last-child(7) /* [flag] */" in a style object 1`] = ` +.emotion-0:nth-last-child(7) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":nth-last-child(7) /* [flag] */" in a style string 1`] = ` +.emotion-0:nth-last-child(7) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes warns when using with mixed flag use: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child :nth-child(3) /* [flag] */" in a style object 1`] = ` +.emotion-0:first-child :nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes warns when using with mixed flag use: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child :nth-child(3) /* [flag] */" in a style string 1`] = ` +.emotion-0:first-child :nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes warns when using with mixed flag use: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child span:nth-child(3) /* [flag] */" in a style object 1`] = ` +.emotion-0:first-child span:nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes warns when using with mixed flag use: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child span:nth-child(3) /* [flag] */" in a style string 1`] = ` +.emotion-0:first-child span:nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes warns when using with mixed flag use: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child, :nth-child(3) /* [flag] */" in a style object 1`] = ` +.emotion-0:first-child, +.emotion-0:nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes warns when using with mixed flag use: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child, :nth-child(3) /* [flag] */" in a style string 1`] = ` +.emotion-0:first-child, +.emotion-0:nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes warns when using with mixed flag use: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child, span:nth-child(3) /* [flag] */" in a style object 1`] = ` +.emotion-0:first-child, +.emotion-0 span:nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes warns when using with mixed flag use: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child, span:nth-child(3) /* [flag] */" in a style string 1`] = ` +.emotion-0:first-child, +.emotion-0 span:nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes warns when using with mixed flag use: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child:nth-child(3) /* [flag] */" in a style object 1`] = ` +.emotion-0:first-child:nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes warns when using with mixed flag use: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child:nth-child(3) /* [flag] */" in a style string 1`] = ` +.emotion-0:first-child:nth-child(3) { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes warns when using with mixed flag use: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":nth-child(3) /* [flag] */ :first-child" in a style object 1`] = ` +.emotion-0:nth-child(3) :first-child { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes warns when using with mixed flag use: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":nth-child(3) /* [flag] */ :first-child" in a style string 1`] = ` +.emotion-0:nth-child(3) :first-child { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes warns when using with mixed flag use: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":nth-child(3) /* [flag] */, :first-child" in a style object 1`] = ` +.emotion-0:nth-child(3), +.emotion-0:first-child { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes warns when using with mixed flag use: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":nth-child(3) /* [flag] */, :first-child" in a style string 1`] = ` +.emotion-0:nth-child(3), +.emotion-0:first-child { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes warns when using with mixed flag use: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":nth-child(3) /* [flag] */:first-child" in a style object 1`] = ` +.emotion-0:nth-child(3) :first-child { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes warns when using with mixed flag use: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":nth-child(3) /* [flag] */:first-child" in a style string 1`] = ` +.emotion-0:nth-child(3) :first-child { + color: rebeccapurple; +} + + +`; + +exports[`unsafe pseudo classes warns when using without flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":first-child" 1`] = ` .emotion-0:first-child { color: hotpink; } @@ -50,7 +424,7 @@ exports[`does warn when using :first-child 1`] = ` /> `; -exports[`does warn when using :nth-child(3) 1`] = ` +exports[`unsafe pseudo classes warns when using without flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":nth-child(3)" 1`] = ` .emotion-0:nth-child(3) { color: hotpink; } @@ -60,7 +434,7 @@ exports[`does warn when using :nth-child(3) 1`] = ` /> `; -exports[`does warn when using :nth-last-child(7) 1`] = ` +exports[`unsafe pseudo classes warns when using without flag: /* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */ ":nth-last-child(7)" 1`] = ` .emotion-0:nth-last-child(7) { color: hotpink; } @@ -69,5 +443,3 @@ exports[`does warn when using :nth-last-child(7) 1`] = ` className="emotion-0" /> `; - -exports[`global with css prop 1`] = `null`; diff --git a/packages/core/__tests__/warnings.js b/packages/core/__tests__/warnings.js index a8b2b47c79..1a0f81f772 100644 --- a/packages/core/__tests__/warnings.js +++ b/packages/core/__tests__/warnings.js @@ -50,29 +50,133 @@ it('does warn when invalid values are passed for the content property', () => { }) }) -let unsafePseudoClasses = [ - ':first-child', - ':nth-child(3)', - ':nth-last-child(7)' -] +describe('unsafe pseudo classes', () => { + const ignoreSsrFlag = + '/* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */' + + describe(`warns when using without flag: ${ignoreSsrFlag}`, () => { + const unsafePseudoClasses = [ + ':first-child', + ':nth-child(3)', + ':nth-last-child(7)' + ] + + unsafePseudoClasses.forEach(pseudoClass => { + it(`"${pseudoClass}"`, () => { + const style = css` + ${pseudoClass} { + color: hotpink; + } + ` + const match = (pseudoClass.match(/(:first|:nth|:nth-last)-child/): any) + expect(match).not.toBeNull() + expect(renderer.create().toJSON()).toMatchSnapshot() + expect(console.error).toBeCalledWith( + `The pseudo class "${ + match[0] + }" is potentially unsafe when doing server-side rendering. Try changing it to "${ + match[1] + }-of-type".` + ) + }) + }) + }) -unsafePseudoClasses.forEach(pseudoClass => { - it('does warn when using ' + pseudoClass, () => { - const style = css` - ${pseudoClass} { - color: hotpink; + describe(`does not warn when using with flag: ${ignoreSsrFlag}`, () => { + const ignoredUnsafePseudoClasses = [ + `:first-child ${ignoreSsrFlag}`, + `:nth-child(3) ${ignoreSsrFlag}`, + `:nth-last-child(7) ${ignoreSsrFlag}`, + `:first-child ${ignoreSsrFlag} span`, + `:first-child ${ignoreSsrFlag}, span`, + `:first-child ${ignoreSsrFlag} :nth-child(3) ${ignoreSsrFlag}`, + `:first-child ${ignoreSsrFlag}, :nth-child(3) ${ignoreSsrFlag}`, + `:first-child ${ignoreSsrFlag}:nth-child(3) ${ignoreSsrFlag}`, + `:first-child ${ignoreSsrFlag} span:nth-child(3) ${ignoreSsrFlag}`, + `:first-child ${ignoreSsrFlag}, span:nth-child(3) ${ignoreSsrFlag}` + ] + + ignoredUnsafePseudoClasses.forEach(pseudoClass => { + const styles = { + string: css` + ${pseudoClass} { + color: rebeccapurple; + } + `, + object: { + [pseudoClass]: { + color: 'rebeccapurple' + } + } } - ` - let match = (pseudoClass.match(/:(first|nth|nth-last)-child/): any) - expect(match).not.toBeNull() - expect(renderer.create().toJSON()).toMatchSnapshot() - expect(console.error).toBeCalledWith( - `The pseudo class "${ - match[0] - }" is potentially unsafe when doing server-side rendering. Try changing it to "${ - match[1] - }-of-type"` - ) + + Object.keys(styles).forEach(type => { + it(`"${pseudoClass.replace( + /\/\* \S+ \*\//g, + '/* [flag] */' + )}" in a style ${type}`, () => { + const match = (pseudoClass.match( + /(:first|:nth|:nth-last)-child/ + ): any) + expect(match).not.toBeNull() + expect( + renderer.create().toJSON() + ).toMatchSnapshot() + expect(console.error).not.toBeCalled() + }) + }) + }) + }) + + describe(`warns when using with mixed flag use: ${ignoreSsrFlag}`, () => { + const unsafePseudoClass = ':first-child' + const mixedIgnoredUnsafePseudoClasses = [ + `:nth-child(3) ${ignoreSsrFlag} ${unsafePseudoClass}`, + `:nth-child(3) ${ignoreSsrFlag}, ${unsafePseudoClass}`, + `:nth-child(3) ${ignoreSsrFlag}${unsafePseudoClass}`, + `${unsafePseudoClass} :nth-child(3) ${ignoreSsrFlag}`, + `${unsafePseudoClass}, :nth-child(3) ${ignoreSsrFlag}`, + `${unsafePseudoClass}:nth-child(3) ${ignoreSsrFlag}`, + `${unsafePseudoClass} span:nth-child(3) ${ignoreSsrFlag}`, + `${unsafePseudoClass}, span:nth-child(3) ${ignoreSsrFlag}` + ] + + mixedIgnoredUnsafePseudoClasses.forEach(pseudoClass => { + const styles = { + string: css` + ${pseudoClass} { + color: rebeccapurple; + } + `, + object: { + [pseudoClass]: { + color: 'rebeccapurple' + } + } + } + + Object.keys(styles).forEach(type => { + it(`"${pseudoClass.replace( + /\/\* \S+ \*\//g, + '/* [flag] */' + )}" in a style ${type}`, () => { + const matches = (pseudoClass.match( + /(:first|:nth|:nth-last)-child/g + ): any) + expect(matches).not.toBeNull() + expect( + renderer.create().toJSON() + ).toMatchSnapshot() + matches.forEach(match => { + expect(console.error).toBeCalledWith( + `The pseudo class "${unsafePseudoClass}" is potentially unsafe when doing server-side rendering. Try changing it to "${ + unsafePseudoClass.split('-child')[0] + }-of-type".` + ) + }) + }) + }) + }) }) })