github.com/evanw/esbuild@v0.21.4/internal/css_parser/css_decls_font_family.go (about) 1 package css_parser 2 3 import ( 4 "strings" 5 6 "github.com/evanw/esbuild/internal/css_ast" 7 "github.com/evanw/esbuild/internal/css_lexer" 8 ) 9 10 // These keywords usually require special handling when parsing. 11 12 // Declaring a property to have these values explicitly specifies a particular 13 // defaulting behavior instead of setting the property to that identifier value. 14 // As specified in CSS Values and Units Level 3, all CSS properties can accept 15 // these values. 16 // 17 // For example, "font-family: 'inherit'" sets the font family to the font named 18 // "inherit" while "font-family: inherit" sets the font family to the inherited 19 // value. 20 // 21 // Note that other CSS specifications can define additional CSS-wide keywords, 22 // which we should copy here whenever new ones are created so we can quote those 23 // identifiers to avoid collisions with any newly-created CSS-wide keywords. 24 var cssWideAndReservedKeywords = map[string]bool{ 25 // CSS Values and Units Level 3: https://drafts.csswg.org/css-values-3/#common-keywords 26 "initial": true, // CSS-wide keyword 27 "inherit": true, // CSS-wide keyword 28 "unset": true, // CSS-wide keyword 29 "default": true, // CSS reserved keyword 30 31 // CSS Cascading and Inheritance Level 5: https://drafts.csswg.org/css-cascade-5/#defaulting-keywords 32 "revert": true, // Cascade-dependent keyword 33 "revert-layer": true, // Cascade-dependent keyword 34 } 35 36 // Font family names that happen to be the same as a keyword value must be 37 // quoted to prevent confusion with the keywords with the same names. UAs must 38 // not consider these keywords as matching the <family-name> type. 39 // Specification: https://drafts.csswg.org/css-fonts/#generic-font-families 40 var genericFamilyNames = map[string]bool{ 41 "serif": true, 42 "sans-serif": true, 43 "cursive": true, 44 "fantasy": true, 45 "monospace": true, 46 "system-ui": true, 47 "emoji": true, 48 "math": true, 49 "fangsong": true, 50 "ui-serif": true, 51 "ui-sans-serif": true, 52 "ui-monospace": true, 53 "ui-rounded": true, 54 } 55 56 // Specification: https://drafts.csswg.org/css-fonts/#font-family-prop 57 func (p *parser) mangleFontFamily(tokens []css_ast.Token) ([]css_ast.Token, bool) { 58 result, rest, ok := p.mangleFamilyNameOrGenericName(nil, tokens) 59 if !ok { 60 return nil, false 61 } 62 63 for len(rest) > 0 && rest[0].Kind == css_lexer.TComma { 64 result, rest, ok = p.mangleFamilyNameOrGenericName(append(result, rest[0]), rest[1:]) 65 if !ok { 66 return nil, false 67 } 68 } 69 70 if len(rest) > 0 { 71 return nil, false 72 } 73 74 return result, true 75 } 76 77 func (p *parser) mangleFamilyNameOrGenericName(result []css_ast.Token, tokens []css_ast.Token) ([]css_ast.Token, []css_ast.Token, bool) { 78 if len(tokens) > 0 { 79 t := tokens[0] 80 81 // Handle <generic-family> 82 if t.Kind == css_lexer.TIdent && genericFamilyNames[t.Text] { 83 return append(result, t), tokens[1:], true 84 } 85 86 // Handle <family-name> 87 if t.Kind == css_lexer.TString { 88 // "If a sequence of identifiers is given as a <family-name>, the computed 89 // value is the name converted to a string by joining all the identifiers 90 // in the sequence by single spaces." 91 // 92 // More information: https://mathiasbynens.be/notes/unquoted-font-family 93 names := strings.Split(t.Text, " ") 94 for _, name := range names { 95 if !isValidCustomIdent(name, genericFamilyNames) { 96 return append(result, t), tokens[1:], true 97 } 98 } 99 for i, name := range names { 100 var whitespace css_ast.WhitespaceFlags 101 if i != 0 || !p.options.minifyWhitespace { 102 whitespace = css_ast.WhitespaceBefore 103 } 104 result = append(result, css_ast.Token{ 105 Loc: t.Loc, 106 Kind: css_lexer.TIdent, 107 Text: name, 108 Whitespace: whitespace, 109 }) 110 } 111 return result, tokens[1:], true 112 } 113 114 // "Font family names other than generic families must either be given 115 // quoted as <string>s, or unquoted as a sequence of one or more 116 // <custom-ident>." 117 if t.Kind == css_lexer.TIdent { 118 for { 119 if !isValidCustomIdent(t.Text, genericFamilyNames) { 120 return nil, nil, false 121 } 122 result = append(result, t) 123 tokens = tokens[1:] 124 if len(tokens) == 0 || tokens[0].Kind != css_lexer.TIdent { 125 break 126 } 127 t = tokens[0] 128 } 129 return result, tokens, true 130 } 131 } 132 133 // Anything other than the cases listed above causes us to bail 134 return nil, nil, false 135 } 136 137 // Specification: https://drafts.csswg.org/css-values-4/#custom-idents 138 func isValidCustomIdent(text string, predefinedKeywords map[string]bool) bool { 139 loweredText := strings.ToLower(text) 140 141 if predefinedKeywords[loweredText] { 142 return false 143 } 144 if cssWideAndReservedKeywords[loweredText] { 145 return false 146 } 147 if loweredText == "" { 148 return false 149 } 150 151 // validate if it contains characters which needs to be escaped 152 if !css_lexer.WouldStartIdentifierWithoutEscapes(text) { 153 return false 154 } 155 for _, c := range text { 156 if !css_lexer.IsNameContinue(c) { 157 return false 158 } 159 } 160 161 return true 162 }