Skip to content

Commit d8c3f87

Browse files
committed
css: parse and print media queries
1 parent 6e75bc7 commit d8c3f87

File tree

9 files changed

+950
-25
lines changed

9 files changed

+950
-25
lines changed

internal/css_ast/css_ast.go

Lines changed: 223 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ func (a *RBadDeclaration) Equal(rule R, check *CrossFileEqualityCheck) bool {
694694
}
695695

696696
func (r *RBadDeclaration) Hash() (uint32, bool) {
697-
hash := uint32(11)
697+
hash := uint32(7)
698698
hash = HashTokens(hash, r.Tokens)
699699
return hash, true
700700
}
@@ -709,7 +709,7 @@ func (a *RComment) Equal(rule R, check *CrossFileEqualityCheck) bool {
709709
}
710710

711711
func (r *RComment) Hash() (uint32, bool) {
712-
hash := uint32(12)
712+
hash := uint32(8)
713713
hash = helpers.HashCombineString(hash, r.Text)
714714
return hash, true
715715
}
@@ -741,7 +741,7 @@ func (a *RAtLayer) Equal(rule R, check *CrossFileEqualityCheck) bool {
741741
}
742742

743743
func (r *RAtLayer) Hash() (uint32, bool) {
744-
hash := uint32(13)
744+
hash := uint32(9)
745745
hash = helpers.HashCombine(hash, uint32(len(r.Names)))
746746
for _, parts := range r.Names {
747747
hash = helpers.HashCombine(hash, uint32(len(parts)))
@@ -753,6 +753,226 @@ func (r *RAtLayer) Hash() (uint32, bool) {
753753
return hash, true
754754
}
755755

756+
type RAtMedia struct {
757+
AtToken string
758+
Queries []MediaQuery
759+
Rules []Rule
760+
CloseBraceLoc logger.Loc
761+
}
762+
763+
func (a *RAtMedia) Equal(rule R, check *CrossFileEqualityCheck) bool {
764+
b, ok := rule.(*RAtMedia)
765+
return ok && MediaQueriesEqual(a.Queries, b.Queries, check) && RulesEqual(a.Rules, b.Rules, check)
766+
}
767+
768+
func (r *RAtMedia) Hash() (uint32, bool) {
769+
hash := uint32(10)
770+
hash = HashMediaQueries(hash, r.Queries)
771+
hash = HashRules(hash, r.Rules)
772+
return hash, true
773+
}
774+
775+
type MediaQuery struct {
776+
Loc logger.Loc
777+
Data MQ
778+
}
779+
780+
type MQ interface {
781+
Equal(query MQ, check *CrossFileEqualityCheck) bool
782+
Hash() uint32
783+
}
784+
785+
func MediaQueriesEqual(a []MediaQuery, b []MediaQuery, check *CrossFileEqualityCheck) bool {
786+
if len(a) != len(b) {
787+
return false
788+
}
789+
for i, ai := range a {
790+
if !ai.Data.Equal(b[i].Data, check) {
791+
return false
792+
}
793+
}
794+
return true
795+
}
796+
797+
func HashMediaQueries(hash uint32, queries []MediaQuery) uint32 {
798+
hash = helpers.HashCombine(hash, uint32(len(queries)))
799+
for _, q := range queries {
800+
hash = helpers.HashCombine(hash, q.Data.Hash())
801+
}
802+
return hash
803+
}
804+
805+
type MQTypeOp uint8
806+
807+
const (
808+
MQTypeOpNone MQTypeOp = iota
809+
MQTypeOpNot
810+
MQTypeOpOnly
811+
)
812+
813+
type MQType struct {
814+
Op MQTypeOp
815+
Type string
816+
AndOrNull MediaQuery
817+
}
818+
819+
func (q *MQType) Equal(query MQ, check *CrossFileEqualityCheck) bool {
820+
p, ok := query.(*MQType)
821+
return ok && q.Op == p.Op && q.Type == p.Type
822+
}
823+
824+
func (q *MQType) Hash() uint32 {
825+
hash := uint32(0)
826+
hash = helpers.HashCombine(hash, uint32(q.Op))
827+
hash = helpers.HashCombineString(hash, q.Type)
828+
return hash
829+
}
830+
831+
type MQNot struct {
832+
Inner MediaQuery
833+
}
834+
835+
func (q *MQNot) Equal(query MQ, check *CrossFileEqualityCheck) bool {
836+
p, ok := query.(*MQNot)
837+
return ok && q.Inner.Data.Equal(p.Inner.Data, check)
838+
}
839+
840+
func (q *MQNot) Hash() uint32 {
841+
hash := uint32(1)
842+
hash = helpers.HashCombine(hash, q.Inner.Data.Hash())
843+
return hash
844+
}
845+
846+
type MQBinaryOp uint8
847+
848+
const (
849+
MQBinaryOpAnd MQBinaryOp = iota
850+
MQBinaryOpOr
851+
)
852+
853+
type MQBinary struct {
854+
Op MQBinaryOp
855+
Terms []MediaQuery
856+
}
857+
858+
func (q *MQBinary) Equal(query MQ, check *CrossFileEqualityCheck) bool {
859+
p, ok := query.(*MQBinary)
860+
return ok && q.Op == p.Op && MediaQueriesEqual(q.Terms, p.Terms, check)
861+
}
862+
863+
func (q *MQBinary) Hash() uint32 {
864+
hash := uint32(2)
865+
hash = helpers.HashCombine(hash, uint32(q.Op))
866+
hash = HashMediaQueries(hash, q.Terms)
867+
return hash
868+
}
869+
870+
type MQGeneralEnclosed struct {
871+
Tokens []Token
872+
}
873+
874+
func (q *MQGeneralEnclosed) Equal(query MQ, check *CrossFileEqualityCheck) bool {
875+
p, ok := query.(*MQGeneralEnclosed)
876+
return ok && TokensEqual(q.Tokens, p.Tokens, check)
877+
}
878+
879+
func (q *MQGeneralEnclosed) Hash() uint32 {
880+
hash := uint32(3)
881+
hash = HashTokens(hash, q.Tokens)
882+
return hash
883+
}
884+
885+
type MQPlainOrBoolean struct {
886+
Name string
887+
ValueOrNil []Token
888+
}
889+
890+
func (q *MQPlainOrBoolean) Equal(query MQ, check *CrossFileEqualityCheck) bool {
891+
p, ok := query.(*MQPlainOrBoolean)
892+
return ok && q.Name == p.Name && TokensEqual(q.ValueOrNil, p.ValueOrNil, check)
893+
}
894+
895+
func (q *MQPlainOrBoolean) Hash() uint32 {
896+
hash := uint32(4)
897+
hash = helpers.HashCombineString(hash, q.Name)
898+
hash = HashTokens(hash, q.ValueOrNil)
899+
return hash
900+
}
901+
902+
type MQRange struct {
903+
Before []Token
904+
Name string
905+
After []Token
906+
NameLoc logger.Loc
907+
BeforeCmp MQCmp
908+
AfterCmp MQCmp
909+
}
910+
911+
func (q *MQRange) Equal(query MQ, check *CrossFileEqualityCheck) bool {
912+
p, ok := query.(*MQRange)
913+
return ok && q.BeforeCmp == p.BeforeCmp && q.AfterCmp == p.AfterCmp && q.Name == p.Name &&
914+
TokensEqual(q.Before, p.Before, check) && TokensEqual(q.After, p.After, check)
915+
}
916+
917+
func (q *MQRange) Hash() uint32 {
918+
hash := uint32(5)
919+
hash = HashTokens(hash, q.Before)
920+
hash = helpers.HashCombine(hash, uint32(q.BeforeCmp))
921+
hash = helpers.HashCombineString(hash, q.Name)
922+
hash = helpers.HashCombine(hash, uint32(q.AfterCmp))
923+
hash = HashTokens(hash, q.After)
924+
return hash
925+
}
926+
927+
type MQCmp uint8
928+
929+
const (
930+
MQCmpNone MQCmp = iota
931+
MQCmpEq
932+
MQCmpLt
933+
MQCmpLe
934+
MQCmpGt
935+
MQCmpGe
936+
)
937+
938+
func (cmp MQCmp) String() string {
939+
switch cmp {
940+
case MQCmpLt:
941+
return "<"
942+
case MQCmpLe:
943+
return "<="
944+
case MQCmpGt:
945+
return ">"
946+
case MQCmpGe:
947+
return ">="
948+
}
949+
return "="
950+
}
951+
952+
func (cmp MQCmp) Dir() int {
953+
switch cmp {
954+
case MQCmpLt, MQCmpLe:
955+
return -1
956+
case MQCmpGt, MQCmpGe:
957+
return 1
958+
}
959+
return 0
960+
}
961+
962+
func (cmp MQCmp) Flip() MQCmp {
963+
switch cmp {
964+
case MQCmpLt:
965+
return MQCmpGe
966+
case MQCmpLe:
967+
return MQCmpGt
968+
case MQCmpGt:
969+
return MQCmpLe
970+
case MQCmpGe:
971+
return MQCmpLt
972+
}
973+
return cmp
974+
}
975+
756976
type ComplexSelector struct {
757977
Selectors []CompoundSelector
758978
}

internal/css_lexer/css_lexer.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const (
3838
TDelimEquals
3939
TDelimExclamation
4040
TDelimGreaterThan
41+
TDelimLessThan
4142
TDelimMinus
4243
TDelimPlus
4344
TDelimSlash
@@ -84,6 +85,7 @@ var tokenToString = []string{
8485
"\"=\"",
8586
"\"!\"",
8687
"\">\"",
88+
"\"<\"",
8789
"\"-\"",
8890
"\"+\"",
8991
"\"/\"",
@@ -415,7 +417,7 @@ func (lexer *lexer) next() {
415417
lexer.Token.Kind = TCDO
416418
} else {
417419
lexer.step()
418-
lexer.Token.Kind = TDelim
420+
lexer.Token.Kind = TDelimLessThan
419421
}
420422

421423
case '@':

internal/css_parser/css_nesting.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ func (p *parser) lowerNestingInRule(rule css_ast.Rule, results []css_ast.Rule) [
9191
rules = p.lowerNestingInRule(child, rules)
9292
}
9393
r.Rules = rules
94+
95+
case *css_ast.RAtMedia:
96+
var rules []css_ast.Rule
97+
for _, child := range r.Rules {
98+
rules = p.lowerNestingInRule(child, rules)
99+
}
100+
r.Rules = rules
94101
}
95102

96103
return append(results, rule)
@@ -288,6 +295,29 @@ func (p *parser) lowerNestingInRuleWithContext(rule css_ast.Rule, context *lower
288295
}
289296
r.Rules = p.lowerNestingInRulesAndReturnRemaining(r.Rules, &childContext)
290297

298+
// "div { @supports (color: red) { color: red } }" "@supports (color: red) { div { color: red } }"
299+
if len(r.Rules) > 0 {
300+
childContext.loweredRules = append([]css_ast.Rule{{Loc: rule.Loc, Data: &css_ast.RSelector{
301+
Selectors: context.parentSelectorsWithPseudo,
302+
Rules: r.Rules,
303+
}}}, childContext.loweredRules...)
304+
}
305+
306+
// "div { @supports (color: red) { &:hover { color: red } } }" "@supports (color: red) { div:hover { color: red } }"
307+
if len(childContext.loweredRules) > 0 {
308+
r.Rules = childContext.loweredRules
309+
context.loweredRules = append(context.loweredRules, rule)
310+
}
311+
312+
return css_ast.Rule{}
313+
314+
case *css_ast.RAtMedia:
315+
childContext := lowerNestingContext{
316+
parentSelectorsWithPseudo: context.parentSelectorsWithPseudo,
317+
parentSelectorsNoPseudo: context.parentSelectorsNoPseudo,
318+
}
319+
r.Rules = p.lowerNestingInRulesAndReturnRemaining(r.Rules, &childContext)
320+
291321
// "div { @media screen { color: red } }" "@media screen { div { color: red } }"
292322
if len(r.Rules) > 0 {
293323
childContext.loweredRules = append([]css_ast.Rule{{Loc: rule.Loc, Data: &css_ast.RSelector{

0 commit comments

Comments
 (0)