UNPKG

3.78 kBJavaScriptView Raw
1/*
2 * The MIT License (MIT)
3 *
4 * Copyright (c) 2015 - present Instructure, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24
25/**
26 * ---
27 * category: utilities/themes
28 * ---
29 * Parses a CSS string into an AST object
30 * @module parseCss
31 * @param {String} cssText CSS string to parse
32 * @returns {Object} AST for the CSS string
33 */
34function parseCss(cssText = '') {
35 const cleaned = cleanCss(cssText)
36 return parseLexed(lex(cleaned), cleaned)
37}
38
39/**
40 * CSSRule types (https://developer.mozilla.org/en-US/docs/Web/API/CSSRule)
41 */
42const ruleTypes = {
43 style: 1,
44 keyframes: 7,
45 media: 4
46}
47
48/**
49 * Removes comments and import statements from a CSS string
50 * (to prep for parsing and applying transforms)
51 * @param {String} cssText CSS string to parse
52 * @returns {String} cleaned CSS string
53 */
54function cleanCss(text = '') {
55 // remove comments and imports
56 return text
57 .replace(/\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim, '')
58 .replace(/@import[^;]*;/gim, '')
59}
60
61function lex(text) {
62 const rootNode = {
63 start: 0,
64 end: text.length
65 }
66 let node = rootNode
67 const chars = text.split('')
68
69 chars.forEach((char, i) => {
70 switch (char) {
71 case '{': {
72 if (!node.rules) {
73 node.rules = []
74 }
75 const parent = node
76 const previous = parent.rules[parent.rules.length - 1]
77 node = {
78 start: i + 1,
79 parent,
80 previous
81 }
82 parent.rules.push(node)
83 break
84 }
85 case '}': {
86 node.end = i + 1
87 node = node.parent || rootNode
88 break
89 }
90 default: {
91 break
92 }
93 }
94 })
95 return rootNode
96}
97
98function parseSelector(node, text) {
99 const start = node.previous ? node.previous.end : node.parent.start
100 const end = node.start - 1
101
102 let selector = text.substring(start, end)
103
104 selector = selector.replace(/\s+/g, ' ')
105 selector = selector.substring(selector.lastIndexOf(';') + 1)
106
107 return selector.trim()
108}
109
110function parseRuleType(selector) {
111 if (selector.indexOf('@') === 0) {
112 if (selector.indexOf('@media') === 0) {
113 return ruleTypes.media
114 } else if (selector.match(/^@[^\s]*keyframes/)) {
115 return ruleTypes.keyframes
116 }
117 } else {
118 return ruleTypes.style
119 }
120}
121
122function parseLexed(node, text = '') {
123 /* eslint-disable no-param-reassign */
124
125 if (node.parent) {
126 node.selector = parseSelector(node, text)
127 node.type = parseRuleType(node.selector)
128 }
129
130 node.cssText = text.substring(node.start, node.end - 1).trim()
131
132 if (node.rules && node.rules.length > 0) {
133 node.rules = node.rules.map((rule) => parseLexed(rule, text))
134 }
135
136 /* eslint-enable no-param-reassign */
137
138 return node
139}
140
141export default parseCss
142export { parseCss, cleanCss, ruleTypes }