userland: add archive tools and pinned GoAWK, esbuild, and QuickJS
This commit is contained in:
@@ -0,0 +1,511 @@
|
||||
package css_parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/evanw/esbuild/internal/compat"
|
||||
"github.com/evanw/esbuild/internal/css_ast"
|
||||
"github.com/evanw/esbuild/internal/logger"
|
||||
)
|
||||
|
||||
func (p *parser) lowerNestingInRule(rule css_ast.Rule, results []css_ast.Rule) []css_ast.Rule {
|
||||
switch r := rule.Data.(type) {
|
||||
case *css_ast.RSelector:
|
||||
scope := func(loc logger.Loc) css_ast.ComplexSelector {
|
||||
return css_ast.ComplexSelector{
|
||||
Selectors: []css_ast.CompoundSelector{{
|
||||
SubclassSelectors: []css_ast.SubclassSelector{{
|
||||
Range: logger.Range{Loc: loc},
|
||||
Data: &css_ast.SSPseudoClass{Name: "scope"},
|
||||
}},
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
parentSelectorsWithPseudo := make([]css_ast.ComplexSelector, 0, len(r.Selectors))
|
||||
parentSelectorsNoPseudo := make([]css_ast.ComplexSelector, 0, len(r.Selectors))
|
||||
for i, sel := range r.Selectors {
|
||||
// Top-level "&" should be replaced with ":scope" to avoid recursion.
|
||||
// From https://www.w3.org/TR/css-nesting-1/#nest-selector:
|
||||
//
|
||||
// "When used in the selector of a nested style rule, the nesting
|
||||
// selector represents the elements matched by the parent rule. When
|
||||
// used in any other context, it represents the same elements as
|
||||
// :scope in that context (unless otherwise defined)."
|
||||
//
|
||||
substituted := make([]css_ast.CompoundSelector, 0, len(sel.Selectors))
|
||||
for _, x := range sel.Selectors {
|
||||
substituted = p.substituteAmpersandsInCompoundSelector(x, scope, substituted, keepLeadingCombinator)
|
||||
}
|
||||
r.Selectors[i] = css_ast.ComplexSelector{Selectors: substituted}
|
||||
|
||||
// Filter out pseudo elements because they are ignored by nested style
|
||||
// rules. This is because pseudo-elements are not valid within :is():
|
||||
// https://www.w3.org/TR/selectors-4/#matches-pseudo. This restriction
|
||||
// may be relaxed in the future, but this restriction has shipped so
|
||||
// we're stuck with it: https://github.com/w3c/csswg-drafts/issues/7433.
|
||||
//
|
||||
// Note: This is only for the parent selector list that is used to
|
||||
// substitute "&" within child rules. Do not filter out the pseudo
|
||||
// element from the top-level selector list.
|
||||
if !sel.UsesPseudoElement() {
|
||||
parentSelectorsNoPseudo = append(parentSelectorsNoPseudo, css_ast.ComplexSelector{Selectors: substituted})
|
||||
}
|
||||
|
||||
// This filtering is only done conditionally because it seems to only
|
||||
// apply sometimes. Specifically it doesn't seem to apply when the
|
||||
// nested rule is an at-rule. So we use the unfiltered list in that
|
||||
// case. See: https://github.com/evanw/esbuild/issues/4265
|
||||
parentSelectorsWithPseudo = append(parentSelectorsWithPseudo, css_ast.ComplexSelector{Selectors: substituted})
|
||||
}
|
||||
|
||||
// Emit this selector before its nested children
|
||||
start := len(results)
|
||||
results = append(results, rule)
|
||||
|
||||
// Lower all children and filter out ones that become empty
|
||||
context := lowerNestingContext{
|
||||
parentSelectorsWithPseudo: parentSelectorsWithPseudo,
|
||||
parentSelectorsNoPseudo: parentSelectorsNoPseudo,
|
||||
loweredRules: results,
|
||||
}
|
||||
r.Rules = p.lowerNestingInRulesAndReturnRemaining(r.Rules, &context)
|
||||
|
||||
// Omit this selector entirely if it's now empty
|
||||
if len(r.Rules) == 0 {
|
||||
copy(context.loweredRules[start:], context.loweredRules[start+1:])
|
||||
context.loweredRules = context.loweredRules[:len(context.loweredRules)-1]
|
||||
}
|
||||
return context.loweredRules
|
||||
|
||||
case *css_ast.RKnownAt:
|
||||
var rules []css_ast.Rule
|
||||
for _, child := range r.Rules {
|
||||
rules = p.lowerNestingInRule(child, rules)
|
||||
}
|
||||
r.Rules = rules
|
||||
|
||||
case *css_ast.RAtLayer:
|
||||
var rules []css_ast.Rule
|
||||
for _, child := range r.Rules {
|
||||
rules = p.lowerNestingInRule(child, rules)
|
||||
}
|
||||
r.Rules = rules
|
||||
|
||||
case *css_ast.RAtMedia:
|
||||
var rules []css_ast.Rule
|
||||
for _, child := range r.Rules {
|
||||
rules = p.lowerNestingInRule(child, rules)
|
||||
}
|
||||
r.Rules = rules
|
||||
}
|
||||
|
||||
return append(results, rule)
|
||||
}
|
||||
|
||||
// Lower all children and filter out ones that become empty
|
||||
func (p *parser) lowerNestingInRulesAndReturnRemaining(rules []css_ast.Rule, context *lowerNestingContext) []css_ast.Rule {
|
||||
n := 0
|
||||
for _, child := range rules {
|
||||
child = p.lowerNestingInRuleWithContext(child, context)
|
||||
if child.Data != nil {
|
||||
rules[n] = child
|
||||
n++
|
||||
}
|
||||
}
|
||||
return rules[:n]
|
||||
}
|
||||
|
||||
func compoundSelectorTermCount(sel css_ast.CompoundSelector) int {
|
||||
count := 0
|
||||
for _, ss := range sel.SubclassSelectors {
|
||||
count++
|
||||
if list, ok := ss.Data.(*css_ast.SSPseudoClassWithSelectorList); ok {
|
||||
count += complexSelectorTermCount(list.Selectors)
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func complexSelectorTermCount(selectors []css_ast.ComplexSelector) int {
|
||||
count := 0
|
||||
for _, sel := range selectors {
|
||||
for _, inner := range sel.Selectors {
|
||||
count += compoundSelectorTermCount(inner)
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (p *parser) addExpansionError(loc logger.Loc, n int) {
|
||||
p.log.AddErrorWithNotes(&p.tracker, logger.Range{Loc: loc}, "CSS nesting is causing too much expansion",
|
||||
[]logger.MsgData{{Text: fmt.Sprintf("CSS nesting expansion was terminated because a rule was generated with %d selectors. "+
|
||||
"This limit exists to prevent esbuild from using too much time and/or memory. "+
|
||||
"Please change your CSS to use fewer levels of nesting.", n)}})
|
||||
}
|
||||
|
||||
type lowerNestingContext struct {
|
||||
parentSelectorsWithPseudo []css_ast.ComplexSelector
|
||||
parentSelectorsNoPseudo []css_ast.ComplexSelector
|
||||
loweredRules []css_ast.Rule
|
||||
}
|
||||
|
||||
func (p *parser) lowerNestingInRuleWithContext(rule css_ast.Rule, context *lowerNestingContext) css_ast.Rule {
|
||||
switch r := rule.Data.(type) {
|
||||
case *css_ast.RSelector:
|
||||
oldSelectorsLen := len(r.Selectors)
|
||||
oldSelectorsComplexity := complexSelectorTermCount(r.Selectors)
|
||||
|
||||
// "a { & b {} }" => "a b {}"
|
||||
// "a { &b {} }" => "a:is(b) {}"
|
||||
// "a { &:hover {} }" => "a:hover {}"
|
||||
// ".x { &b {} }" => "b.x {}"
|
||||
// "a, b { .c, d {} }" => ":is(a, b) :is(.c, d) {}"
|
||||
// "a, b { &.c, & d, e & {} }" => ":is(a, b).c, :is(a, b) d, e :is(a, b) {}"
|
||||
|
||||
// Pass 1: Canonicalize and analyze our selectors
|
||||
for i := range r.Selectors {
|
||||
sel := &r.Selectors[i]
|
||||
|
||||
// Inject the implicit "&" now for simplicity later on
|
||||
if sel.IsRelative() {
|
||||
sel.Selectors = append([]css_ast.CompoundSelector{{NestingSelectorLocs: []logger.Loc{rule.Loc}}}, sel.Selectors...)
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: Substitute "&" for the parent selector
|
||||
if !p.options.unsupportedCSSFeatures.Has(compat.IsPseudoClass) || len(context.parentSelectorsNoPseudo) <= 1 {
|
||||
// If we can use ":is", or we don't have to because there's only one
|
||||
// parent selector, or we are using ":is()" to match zero parent selectors
|
||||
// (even if ":is" is unsupported), then substituting "&" for the parent
|
||||
// selector is easy.
|
||||
for i := range r.Selectors {
|
||||
complex := &r.Selectors[i]
|
||||
results := make([]css_ast.CompoundSelector, 0, len(complex.Selectors))
|
||||
parent := p.multipleComplexSelectorsToSingleComplexSelector(context.parentSelectorsNoPseudo)
|
||||
for _, compound := range complex.Selectors {
|
||||
results = p.substituteAmpersandsInCompoundSelector(compound, parent, results, keepLeadingCombinator)
|
||||
}
|
||||
complex.Selectors = results
|
||||
}
|
||||
} else {
|
||||
// Otherwise if we can't use ":is", the transform is more complicated.
|
||||
// Avoiding ":is" can lead to a combinatorial explosion of cases so we
|
||||
// want to avoid this if possible. For example:
|
||||
//
|
||||
// .first, .second, .third {
|
||||
// & > & {
|
||||
// color: red;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// If we can use ":is" (the easy case above) then we can do this:
|
||||
//
|
||||
// :is(.first, .second, .third) > :is(.first, .second, .third) {
|
||||
// color: red;
|
||||
// }
|
||||
//
|
||||
// But if we can't use ":is" then we have to do this instead:
|
||||
//
|
||||
// .first > .first,
|
||||
// .first > .second,
|
||||
// .first > .third,
|
||||
// .second > .first,
|
||||
// .second > .second,
|
||||
// .second > .third,
|
||||
// .third > .first,
|
||||
// .third > .second,
|
||||
// .third > .third {
|
||||
// color: red;
|
||||
// }
|
||||
//
|
||||
// That combinatorial explosion is what the loop below implements. Note
|
||||
// that PostCSS's implementation of nesting gets this wrong. It generates
|
||||
// this instead:
|
||||
//
|
||||
// .first > .first,
|
||||
// .second > .second,
|
||||
// .third > .third {
|
||||
// color: red;
|
||||
// }
|
||||
//
|
||||
// That's not equivalent, so that's an incorrect transformation.
|
||||
var selectors []css_ast.ComplexSelector
|
||||
var indices []int
|
||||
for {
|
||||
// Every time we encounter another "&", add another dimension
|
||||
offset := 0
|
||||
parent := func(loc logger.Loc) css_ast.ComplexSelector {
|
||||
if offset == len(indices) {
|
||||
indices = append(indices, 0)
|
||||
}
|
||||
index := indices[offset]
|
||||
offset++
|
||||
return context.parentSelectorsNoPseudo[index]
|
||||
}
|
||||
|
||||
// Do the substitution for this particular combination
|
||||
for i := range r.Selectors {
|
||||
complex := r.Selectors[i]
|
||||
results := make([]css_ast.CompoundSelector, 0, len(complex.Selectors))
|
||||
for _, compound := range complex.Selectors {
|
||||
results = p.substituteAmpersandsInCompoundSelector(compound, parent, results, keepLeadingCombinator)
|
||||
}
|
||||
complex.Selectors = results
|
||||
selectors = append(selectors, complex)
|
||||
offset = 0
|
||||
}
|
||||
|
||||
// Do addition with carry on the indices across dimensions
|
||||
carry := len(indices)
|
||||
for carry > 0 {
|
||||
index := &indices[carry-1]
|
||||
if *index+1 < len(context.parentSelectorsNoPseudo) {
|
||||
*index++
|
||||
break
|
||||
}
|
||||
*index = 0
|
||||
carry--
|
||||
}
|
||||
if carry == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
r.Selectors = selectors
|
||||
}
|
||||
|
||||
// Put limits on the combinatorial explosion to avoid using too much time and/or memory
|
||||
if n := len(r.Selectors); n > oldSelectorsLen && n > 0xFF00 {
|
||||
p.addExpansionError(rule.Loc, n)
|
||||
return css_ast.Rule{}
|
||||
}
|
||||
if n := complexSelectorTermCount(r.Selectors); n > oldSelectorsComplexity && n > 0xFF00 {
|
||||
p.addExpansionError(rule.Loc, n)
|
||||
return css_ast.Rule{}
|
||||
}
|
||||
|
||||
// Lower all child rules using our newly substituted selector
|
||||
context.loweredRules = p.lowerNestingInRule(rule, context.loweredRules)
|
||||
return css_ast.Rule{}
|
||||
|
||||
case *css_ast.RKnownAt:
|
||||
childContext := lowerNestingContext{
|
||||
parentSelectorsWithPseudo: context.parentSelectorsWithPseudo,
|
||||
parentSelectorsNoPseudo: context.parentSelectorsNoPseudo,
|
||||
}
|
||||
r.Rules = p.lowerNestingInRulesAndReturnRemaining(r.Rules, &childContext)
|
||||
|
||||
// "div { @supports (color: red) { color: red } }" "@supports (color: red) { div { color: red } }"
|
||||
if len(r.Rules) > 0 {
|
||||
childContext.loweredRules = append([]css_ast.Rule{{Loc: rule.Loc, Data: &css_ast.RSelector{
|
||||
Selectors: context.parentSelectorsWithPseudo,
|
||||
Rules: r.Rules,
|
||||
}}}, childContext.loweredRules...)
|
||||
}
|
||||
|
||||
// "div { @supports (color: red) { &:hover { color: red } } }" "@supports (color: red) { div:hover { color: red } }"
|
||||
if len(childContext.loweredRules) > 0 {
|
||||
r.Rules = childContext.loweredRules
|
||||
context.loweredRules = append(context.loweredRules, rule)
|
||||
}
|
||||
|
||||
return css_ast.Rule{}
|
||||
|
||||
case *css_ast.RAtMedia:
|
||||
childContext := lowerNestingContext{
|
||||
parentSelectorsWithPseudo: context.parentSelectorsWithPseudo,
|
||||
parentSelectorsNoPseudo: context.parentSelectorsNoPseudo,
|
||||
}
|
||||
r.Rules = p.lowerNestingInRulesAndReturnRemaining(r.Rules, &childContext)
|
||||
|
||||
// "div { @media screen { color: red } }" "@media screen { div { color: red } }"
|
||||
if len(r.Rules) > 0 {
|
||||
childContext.loweredRules = append([]css_ast.Rule{{Loc: rule.Loc, Data: &css_ast.RSelector{
|
||||
Selectors: context.parentSelectorsWithPseudo,
|
||||
Rules: r.Rules,
|
||||
}}}, childContext.loweredRules...)
|
||||
}
|
||||
|
||||
// "div { @media screen { &:hover { color: red } } }" "@media screen { div:hover { color: red } }"
|
||||
if len(childContext.loweredRules) > 0 {
|
||||
r.Rules = childContext.loweredRules
|
||||
context.loweredRules = append(context.loweredRules, rule)
|
||||
}
|
||||
|
||||
return css_ast.Rule{}
|
||||
|
||||
case *css_ast.RAtLayer:
|
||||
// Lower all children and filter out ones that become empty
|
||||
childContext := lowerNestingContext{
|
||||
parentSelectorsWithPseudo: context.parentSelectorsWithPseudo,
|
||||
parentSelectorsNoPseudo: context.parentSelectorsNoPseudo,
|
||||
}
|
||||
r.Rules = p.lowerNestingInRulesAndReturnRemaining(r.Rules, &childContext)
|
||||
|
||||
// "div { @layer foo { color: red } }" "@layer foo { div { color: red } }"
|
||||
if len(r.Rules) > 0 {
|
||||
childContext.loweredRules = append([]css_ast.Rule{{Loc: rule.Loc, Data: &css_ast.RSelector{
|
||||
Selectors: context.parentSelectorsWithPseudo,
|
||||
Rules: r.Rules,
|
||||
}}}, childContext.loweredRules...)
|
||||
}
|
||||
|
||||
// "div { @layer foo { &:hover { color: red } } }" "@layer foo { div:hover { color: red } }"
|
||||
// "div { @layer foo {} }" => "@layer foo {}" (layers have side effects, so don't remove empty ones)
|
||||
r.Rules = childContext.loweredRules
|
||||
context.loweredRules = append(context.loweredRules, rule)
|
||||
return css_ast.Rule{}
|
||||
}
|
||||
|
||||
return rule
|
||||
}
|
||||
|
||||
type leadingCombinatorStrip uint8
|
||||
|
||||
const (
|
||||
keepLeadingCombinator leadingCombinatorStrip = iota
|
||||
stripLeadingCombinator
|
||||
)
|
||||
|
||||
func (p *parser) substituteAmpersandsInCompoundSelector(
|
||||
sel css_ast.CompoundSelector,
|
||||
replacementFn func(logger.Loc) css_ast.ComplexSelector,
|
||||
results []css_ast.CompoundSelector,
|
||||
strip leadingCombinatorStrip,
|
||||
) []css_ast.CompoundSelector {
|
||||
for _, nestingSelectorLoc := range sel.NestingSelectorLocs {
|
||||
replacement := replacementFn(nestingSelectorLoc)
|
||||
|
||||
// Convert the replacement to a single compound selector
|
||||
var single css_ast.CompoundSelector
|
||||
if sel.Combinator.Byte == 0 && (len(replacement.Selectors) == 1 || len(results) == 0) {
|
||||
// ".foo { :hover & {} }" => ":hover .foo {}"
|
||||
// ".foo .bar { &:hover {} }" => ".foo .bar:hover {}"
|
||||
last := len(replacement.Selectors) - 1
|
||||
results = append(results, replacement.Selectors[:last]...)
|
||||
single = replacement.Selectors[last]
|
||||
if strip == stripLeadingCombinator {
|
||||
single.Combinator = css_ast.Combinator{}
|
||||
}
|
||||
sel.Combinator = single.Combinator
|
||||
} else if len(replacement.Selectors) == 1 {
|
||||
// ".foo { > &:hover {} }" => ".foo > .foo:hover {}"
|
||||
single = replacement.Selectors[0]
|
||||
if strip == stripLeadingCombinator {
|
||||
single.Combinator = css_ast.Combinator{}
|
||||
}
|
||||
} else {
|
||||
// ".foo .bar { :hover & {} }" => ":hover :is(.foo .bar) {}"
|
||||
// ".foo .bar { > &:hover {} }" => ".foo .bar > :is(.foo .bar):hover {}"
|
||||
p.reportNestingWithGeneratedPseudoClassIs(nestingSelectorLoc)
|
||||
single = css_ast.CompoundSelector{
|
||||
SubclassSelectors: []css_ast.SubclassSelector{{
|
||||
Range: logger.Range{Loc: nestingSelectorLoc},
|
||||
Data: &css_ast.SSPseudoClassWithSelectorList{
|
||||
Kind: css_ast.PseudoClassIs,
|
||||
Selectors: []css_ast.ComplexSelector{replacement.Clone()},
|
||||
},
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
var subclassSelectorPrefix []css_ast.SubclassSelector
|
||||
|
||||
// Insert the type selector
|
||||
if single.TypeSelector != nil {
|
||||
if sel.TypeSelector != nil {
|
||||
p.reportNestingWithGeneratedPseudoClassIs(nestingSelectorLoc)
|
||||
subclassSelectorPrefix = append(subclassSelectorPrefix, css_ast.SubclassSelector{
|
||||
Range: sel.TypeSelector.Range(),
|
||||
Data: &css_ast.SSPseudoClassWithSelectorList{
|
||||
Kind: css_ast.PseudoClassIs,
|
||||
Selectors: []css_ast.ComplexSelector{{Selectors: []css_ast.CompoundSelector{{TypeSelector: sel.TypeSelector}}}},
|
||||
},
|
||||
})
|
||||
}
|
||||
sel.TypeSelector = single.TypeSelector
|
||||
}
|
||||
|
||||
// Insert the subclass selectors
|
||||
subclassSelectorPrefix = append(subclassSelectorPrefix, single.SubclassSelectors...)
|
||||
|
||||
// Write the changes back
|
||||
if len(subclassSelectorPrefix) > 0 {
|
||||
sel.SubclassSelectors = append(subclassSelectorPrefix, sel.SubclassSelectors...)
|
||||
}
|
||||
}
|
||||
sel.NestingSelectorLocs = nil
|
||||
|
||||
// "div { :is(&.foo) {} }" => ":is(div.foo) {}"
|
||||
for _, ss := range sel.SubclassSelectors {
|
||||
if class, ok := ss.Data.(*css_ast.SSPseudoClassWithSelectorList); ok {
|
||||
outer := make([]css_ast.ComplexSelector, 0, len(class.Selectors))
|
||||
for _, complex := range class.Selectors {
|
||||
inner := make([]css_ast.CompoundSelector, 0, len(complex.Selectors))
|
||||
for _, sel := range complex.Selectors {
|
||||
inner = p.substituteAmpersandsInCompoundSelector(sel, replacementFn, inner, stripLeadingCombinator)
|
||||
}
|
||||
outer = append(outer, css_ast.ComplexSelector{Selectors: inner})
|
||||
}
|
||||
class.Selectors = outer
|
||||
}
|
||||
}
|
||||
|
||||
return append(results, sel)
|
||||
}
|
||||
|
||||
// Turn the list of selectors into a single selector by wrapping lists
|
||||
// without a single element with ":is(...)". Note that this may result
|
||||
// in an empty ":is()" selector (which matches nothing).
|
||||
func (p *parser) multipleComplexSelectorsToSingleComplexSelector(selectors []css_ast.ComplexSelector) func(logger.Loc) css_ast.ComplexSelector {
|
||||
if len(selectors) == 1 {
|
||||
return func(logger.Loc) css_ast.ComplexSelector {
|
||||
return selectors[0]
|
||||
}
|
||||
}
|
||||
|
||||
var leadingCombinator css_ast.Combinator
|
||||
clones := make([]css_ast.ComplexSelector, len(selectors))
|
||||
|
||||
for i, sel := range selectors {
|
||||
// "> a, > b" => "> :is(a, b)" (the caller should have already checked that all leading combinators are the same)
|
||||
leadingCombinator = sel.Selectors[0].Combinator
|
||||
clones[i] = sel.Clone()
|
||||
}
|
||||
|
||||
return func(loc logger.Loc) css_ast.ComplexSelector {
|
||||
return css_ast.ComplexSelector{
|
||||
Selectors: []css_ast.CompoundSelector{{
|
||||
Combinator: leadingCombinator,
|
||||
SubclassSelectors: []css_ast.SubclassSelector{{
|
||||
Range: logger.Range{Loc: loc},
|
||||
Data: &css_ast.SSPseudoClassWithSelectorList{
|
||||
Kind: css_ast.PseudoClassIs,
|
||||
Selectors: clones,
|
||||
},
|
||||
}},
|
||||
}},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) reportNestingWithGeneratedPseudoClassIs(nestingSelectorLoc logger.Loc) {
|
||||
if p.options.unsupportedCSSFeatures.Has(compat.IsPseudoClass) {
|
||||
_, didWarn := p.nestingWarnings[nestingSelectorLoc]
|
||||
if didWarn {
|
||||
// Only warn at each location once
|
||||
return
|
||||
}
|
||||
if p.nestingWarnings == nil {
|
||||
p.nestingWarnings = make(map[logger.Loc]struct{})
|
||||
}
|
||||
p.nestingWarnings[nestingSelectorLoc] = struct{}{}
|
||||
text := "Transforming this CSS nesting syntax is not supported in the configured target environment"
|
||||
if p.options.originalTargetEnv != "" {
|
||||
text = fmt.Sprintf("%s (%s)", text, p.options.originalTargetEnv)
|
||||
}
|
||||
r := logger.Range{Loc: nestingSelectorLoc, Len: 1}
|
||||
p.log.AddIDWithNotes(logger.MsgID_CSS_UnsupportedCSSNesting, logger.Warning, &p.tracker, r, text, []logger.MsgData{{
|
||||
Text: "The nesting transform for this case must generate an \":is(...)\" but the configured target environment does not support the \":is\" pseudo-class."}})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user