import {
compile,
alloc,
dealloc,
next,
delimit,
token,
char,
from,
peek,
position,
slice
} from 'stylis'
const last = arr => (arr.length ? arr[arr.length - 1] : null)
const identifierWithPointTracking = (begin, points, index) => {
let previous = 0
let character = 0
while (true) {
previous = character
character = peek()
if (previous === 38 && character === 12) {
points[index] = 1
}
if (token(character)) {
break
}
next()
}
return slice(begin, position)
}
const toRules = (parsed, points) => {
let index = -1
let character = 44
do {
switch (token(character)) {
case 0:
if (character === 38 && peek() === 12) {
points[index] = 1
}
parsed[index] += identifierWithPointTracking(
position - 1,
points,
index
)
break
case 2:
parsed[index] += delimit(character)
break
case 4:
if (character === 44) {
parsed[++index] = peek() === 58 ? '&\f' : ''
points[index] = parsed[index].length
break
}
default:
parsed[index] += from(character)
}
} while ((character = next()))
return parsed
}
const getRules = (value, points) => dealloc(toRules(alloc(value), points))
const fixedElements = new WeakMap()
export let compat = element => {
if (
element.type !== 'rule' ||
!element.parent ||
element.length < 1
) {
return
}
let { value, parent } = element
let isImplicitRule =
element.column === parent.column && element.line === parent.line
while (parent.type !== 'rule') {
parent = parent.parent
if (!parent) return
}
if (
element.props.length === 1 &&
value.charCodeAt(0) !== 58 &&
!fixedElements.get(parent)
) {
return
}
if (isImplicitRule) {
return
}
fixedElements.set(element, true)
const points = []
const rules = getRules(value, points)
const parentRules = parent.props
for (let i = 0, k = 0; i < rules.length; i++) {
for (let j = 0; j < parentRules.length; j++, k++) {
element.props[k] = points[i]
? rules[i].replace(/&\f/g, parentRules[j])
: `${parentRules[j]} ${rules[i]}`
}
}
}
export let removeLabel = element => {
if (element.type === 'decl') {
var value = element.value
if (
value.charCodeAt(0) === 108 &&
value.charCodeAt(2) === 98
) {
element.return = ''
element.value = ''
}
}
}
const ignoreFlag =
'emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason'
const isIgnoringComment = element =>
element.type === 'comm' && element.children.indexOf(ignoreFlag) > -1
export let createUnsafeSelectorsAlarm = cache => (element, index, children) => {
if (element.type !== 'rule' || cache.compat) return
const unsafePseudoClasses = element.value.match(
/(:first|:nth|:nth-last)-child/g
)
if (unsafePseudoClasses) {
const isNested = !!element.parent
const commentContainer = isNested
? element.parent.children
: children
for (let i = commentContainer.length - 1; i >= 0; i--) {
const node = commentContainer[i]
if (node.line < element.line) {
break
}
if (node.column < element.column) {
if (isIgnoringComment(node)) {
return
}
break
}
}
unsafePseudoClasses.forEach(unsafePseudoClass => {
console.error(
`The pseudo class "${unsafePseudoClass}" is potentially unsafe when doing server-side rendering. Try changing it to "${
unsafePseudoClass.split('-child')[0]
}-of-type".`
)
})
}
}
let isImportRule = element =>
element.type.charCodeAt(1) === 105 && element.type.charCodeAt(0) === 64
const isPrependedWithRegularRules = (index, children) => {
for (let i = index - 1; i >= 0; i--) {
if (!isImportRule(children[i])) {
return true
}
}
return false
}
const nullifyElement = element => {
element.type = ''
element.value = ''
element.return = ''
element.children = ''
element.props = ''
}
export let incorrectImportAlarm = (element, index, children) => {
if (!isImportRule(element)) {
return
}
if (element.parent) {
console.error(
"`@import` rules can't be nested inside other rules. Please move it to the top level and put it before regular rules. Keep in mind that they can only be used within global styles."
)
nullifyElement(element)
} else if (isPrependedWithRegularRules(index, children)) {
console.error(
"`@import` rules can't be after other rules. Please put your `@import` rules before your other rules."
)
nullifyElement(element)
}
}