diff --git a/.changeset/witty-frogs-hear.md b/.changeset/witty-frogs-hear.md new file mode 100644 index 000000000..7ec22ada9 --- /dev/null +++ b/.changeset/witty-frogs-hear.md @@ -0,0 +1,8 @@ +--- +'@emotion/cache': patch +--- + +author: @Andarist +author: @kyoncy + +Fix `/* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */` not disabling the warning when there are multiple blocks of CSS. diff --git a/packages/cache/src/stylis-plugins.js b/packages/cache/src/stylis-plugins.js index 7e8fc507d..43bac0cb7 100644 --- a/packages/cache/src/stylis-plugins.js +++ b/packages/cache/src/stylis-plugins.js @@ -155,22 +155,70 @@ const ignoreFlag = 'emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason' const isIgnoringComment = element => - !!element && - element.type === 'comm' && - element.children.indexOf(ignoreFlag) > -1 + element.type === 'comm' && element.children.indexOf(ignoreFlag) > -1 export let createUnsafeSelectorsAlarm = cache => (element, index, children) => { - if (element.type !== 'rule') return + if (element.type !== 'rule' || cache.compat) return const unsafePseudoClasses = element.value.match( /(:first|:nth|:nth-last)-child/g ) - if (unsafePseudoClasses && cache.compat !== true) { - const prevElement = index > 0 ? children[index - 1] : null - if (prevElement && isIgnoringComment(last(prevElement.children))) { - return + if (unsafePseudoClasses) { + const isNested = element.parent === children[0] + // in nested rules comments become children of the "auto-inserted" rule + // + // considering this input: + // .a { + // .b /* comm */ {} + // color: hotpink; + // } + // we get output corresponding to this: + // .a { + // & { + // /* comm */ + // color: hotpink; + // } + // .b {} + // } + const commentContainer = isNested + ? children[0].children + : // global rule at the root level + children + + for (let i = 0; i < commentContainer.length; i++) { + const node = commentContainer[i] + + if (node.line > element.line) { + break + } + + // it is quite weird but comments are *usually* put at `column: element.column - 1` + // so we seek for the node that is later than the rule's `element` and check the previous element + // this will also match inputs like this: + // .a { + // /* comm */ + // .b {} + // } + // + // but that is fine + // + // it would be the easiest to change the placement of the comment to be the first child of the rule: + // .a { + // .b { /* comm */ } + // } + // with such inputs we wouldn't have to search for the comment at all + // TODO: consider changing this comment placement in the next major version + if (node.column > element.column) { + const previousNode = commentContainer[i - 1] + + if (isIgnoringComment(previousNode)) { + return + } + break + } } + unsafePseudoClasses.forEach(unsafePseudoClass => { console.error( `The pseudo class "${unsafePseudoClass}" is potentially unsafe when doing server-side rendering. Try changing it to "${ diff --git a/packages/react/__tests__/__snapshots__/warnings.js.snap b/packages/react/__tests__/__snapshots__/warnings.js.snap index 0e6ded386..5bd0f885c 100644 --- a/packages/react/__tests__/__snapshots__/warnings.js.snap +++ b/packages/react/__tests__/__snapshots__/warnings.js.snap @@ -32,9 +32,15 @@ exports[`does not warn when valid values are passed for the content property 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] */" 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 */ does not warn when using the flag on a global rule 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 */ does not warn when using the flag on the rule that follows a declaration 1`] = ` +.emotion-0 { + color: hotpink; +} + +.emotion-0>*:first-child { + margin-left: 0; }
`; -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 */ does not warn when using the flag on the rule that follows another rule 1`] = ` +.emotion-0>* { + margin-left: 10px; +} + +.emotion-0>*:first-child { + margin-left: 0; } `; -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 :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 */ does not warn when using the flag on the rule that preceeds a declaration 1`] = ` +.emotion-0 { + color: hotpink; +} + +.emotion-0>*:first-child { + margin-left: 0; } `; -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 :nth-child(3) /* [flag] */" in a style string 1`] = ` +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 */ does not warn when using the flag on the rule that preceeds another rule 1`] = ` +.emotion-0>*:first-child { + margin-left: 0; +} + +.emotion-0>* { + margin-left: 10px; +} + + +`; + +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 */ with object styles :first-child :nth-child(3) 1`] = ` .emotion-0:first-child :nth-child(3) { color: rebeccapurple; } @@ -72,8 +100,8 @@ exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-di /> `; -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 span /* [flag] */" in a style object 1`] = ` -.emotion-0:first-child span { +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 */ with object styles :first-child 1`] = ` +.emotion-0:first-child { color: rebeccapurple; } @@ -82,7 +110,7 @@ exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-di /> `; -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 span /* [flag] */" in a style string 1`] = ` +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 */ with object styles :first-child span 1`] = ` .emotion-0:first-child span { color: rebeccapurple; } @@ -92,7 +120,7 @@ exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-di /> `; -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, :nth-child(3) /* [flag] */" in a style object 1`] = ` +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 */ with object styles :first-child, :nth-child(3) 1`] = ` .emotion-0:first-child, .emotion-0:nth-child(3) { color: rebeccapurple; @@ -103,9 +131,9 @@ exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-di /> `; -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, :nth-child(3) /* [flag] */" in a style string 1`] = ` +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 */ with object styles :first-child, span 1`] = ` .emotion-0:first-child, -.emotion-0:nth-child(3) { +.emotion-0 span { color: rebeccapurple; } @@ -114,9 +142,8 @@ exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-di /> `; -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, span /* [flag] */" in a style object 1`] = ` -.emotion-0:first-child, -.emotion-0 span { +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 */ with object styles :first-child:nth-child(3) 1`] = ` +.emotion-0:first-child:nth-child(3) { color: rebeccapurple; } @@ -125,9 +152,8 @@ exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-di /> `; -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, span /* [flag] */" in a style string 1`] = ` -.emotion-0:first-child, -.emotion-0 span { +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 */ with object styles :not(:first-child) 1`] = ` +.emotion-0:not(:first-child) { color: rebeccapurple; } @@ -136,8 +162,8 @@ exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-di /> `; -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:nth-child(3) /* [flag] */" in a style object 1`] = ` -.emotion-0:first-child:nth-child(3) { +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 */ with object styles :not(:nth-child(3)) 1`] = ` +.emotion-0:not(:nth-child(3)) { color: rebeccapurple; } @@ -146,8 +172,8 @@ exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-di /> `; -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:nth-child(3) /* [flag] */" in a style string 1`] = ` -.emotion-0:first-child:nth-child(3) { +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 */ with object styles :nth-child(3) 1`] = ` +.emotion-0:nth-child(3) { color: rebeccapurple; } @@ -156,8 +182,8 @@ exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-di /> `; -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 */ ":not(:first-child) /* [flag] */" in a style object 1`] = ` -.emotion-0:not(:first-child) { +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 */ with object styles :nth-last-child(7) 1`] = ` +.emotion-0:nth-last-child(7) { color: rebeccapurple; } @@ -166,8 +192,8 @@ exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-di /> `; -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 */ ":not(:first-child) /* [flag] */" in a style string 1`] = ` -.emotion-0:not(:first-child) { +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 */ with string styles :first-child :nth-child(3) 1`] = ` +.emotion-0:first-child :nth-child(3) { color: rebeccapurple; } @@ -176,8 +202,8 @@ exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-di /> `; -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 */ ":not(:nth-child(3)) /* [flag] */" in a style object 1`] = ` -.emotion-0:not(:nth-child(3)) { +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 */ with string styles :first-child 1`] = ` +.emotion-0:first-child { color: rebeccapurple; } @@ -186,8 +212,8 @@ exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-di /> `; -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 */ ":not(:nth-child(3)) /* [flag] */" in a style string 1`] = ` -.emotion-0:not(:nth-child(3)) { +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 */ with string styles :first-child span 1`] = ` +.emotion-0:first-child span { color: rebeccapurple; } @@ -196,7 +222,8 @@ exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-di /> `; -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`] = ` +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 */ with string styles :first-child, :nth-child(3) 1`] = ` +.emotion-0:first-child, .emotion-0:nth-child(3) { color: rebeccapurple; } @@ -206,8 +233,9 @@ exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-di /> `; -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) { +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 */ with string styles :first-child, span 1`] = ` +.emotion-0:first-child, +.emotion-0 span { color: rebeccapurple; } @@ -216,8 +244,38 @@ exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-di /> `; -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) { +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 */ with string styles :first-child:nth-child(3) 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 */ with string styles :not(:first-child) 1`] = ` +.emotion-0:not(: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 */ with string styles :not(:nth-child(3)) 1`] = ` +.emotion-0:not(: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 */ with string styles :nth-child(3) 1`] = ` +.emotion-0:nth-child(3) { color: rebeccapurple; } @@ -226,7 +284,7 @@ exports[`unsafe pseudo classes does not warn when using with flag: /* emotion-di /> `; -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`] = ` +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 */ with string styles :nth-last-child(7) 1`] = ` .emotion-0:nth-last-child(7) { color: rebeccapurple; } diff --git a/packages/react/__tests__/warnings.js b/packages/react/__tests__/warnings.js index 4f4c0a6c0..5454788cc 100644 --- a/packages/react/__tests__/warnings.js +++ b/packages/react/__tests__/warnings.js @@ -2,6 +2,7 @@ /** @jsx jsx */ import 'test-utils/next-env' import { jsx, css, Global, keyframes, ClassNames } from '@emotion/react' +import styled from '@emotion/styled' import renderer from 'react-test-renderer' import { render } from '@testing-library/react' @@ -86,49 +87,137 @@ describe('unsafe pseudo classes', () => { }) describe(`does not warn when using with flag: ${ignoreSsrFlag}`, () => { - const ignoredUnsafePseudoClasses = [ - `:first-child ${ignoreSsrFlag}`, - `:not(:first-child) ${ignoreSsrFlag}`, - `:nth-child(3) ${ignoreSsrFlag}`, - `:not(:nth-child(3)) ${ignoreSsrFlag}`, - `:nth-last-child(7) ${ignoreSsrFlag}`, - `:first-child span ${ignoreSsrFlag}`, - `:first-child, span ${ignoreSsrFlag}`, - `:first-child :nth-child(3) ${ignoreSsrFlag}`, - `:first-child, :nth-child(3) ${ignoreSsrFlag}`, - `:first-child:nth-child(3) ${ignoreSsrFlag}` - ] - - ignoredUnsafePseudoClasses.forEach(pseudoClass => { - const styles = { - string: css` - ${pseudoClass} { + describe.each([ + { + type: 'string', + getStyle: pseudoClass => css` + ${pseudoClass} ${ignoreSsrFlag} { color: rebeccapurple; } - `, - object: { - [pseudoClass]: { + ` + }, + { + type: 'object', + getStyle: pseudoClass => ({ + [`${pseudoClass} ${ignoreSsrFlag}`]: { color: 'rebeccapurple' } - } - } - - 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() }) + } + ])(`with $type styles`, ({ getStyle }) => { + test.each([ + { pseudoClass: `:first-child` }, + { pseudoClass: `:not(:first-child)` }, + { pseudoClass: `:nth-child(3)` }, + { pseudoClass: `:not(:nth-child(3))` }, + { pseudoClass: `:nth-last-child(7)` }, + { pseudoClass: `:first-child span` }, + { pseudoClass: `:first-child, span` }, + { pseudoClass: `:first-child :nth-child(3)` }, + { pseudoClass: `:first-child, :nth-child(3)` }, + { pseudoClass: `:first-child:nth-child(3)` } + ])('$pseudoClass', ({ pseudoClass }) => { + const match = (pseudoClass.match(/(:first|:nth|:nth-last)-child/): any) + expect(match).not.toBeNull() + expect( + renderer.create().toJSON() + ).toMatchSnapshot() + expect(console.error).not.toBeCalled() }) }) + + test('does not warn when using the flag on the rule that follows another rule', () => { + expect( + renderer + .create( +