UNPKG

4.7 kBJavaScriptView Raw
1/*!
2 * Stylus - Selector Parser
3 * Copyright (c) Automattic <developer.wordpress.com>
4 * MIT Licensed
5 */
6
7var COMBINATORS = ['>', '+', '~'];
8
9/**
10 * Initialize a new `SelectorParser`
11 * with the given `str` and selectors `stack`.
12 *
13 * @param {String} str
14 * @param {Array} stack
15 * @param {Array} parts
16 * @api private
17 */
18
19var SelectorParser = module.exports = function SelectorParser(str, stack, parts) {
20 this.str = str;
21 this.stack = stack || [];
22 this.parts = parts || [];
23 this.pos = 0;
24 this.level = 2;
25 this.nested = true;
26 this.ignore = false;
27};
28
29/**
30 * Consume the given `len` and move current position.
31 *
32 * @param {Number} len
33 * @api private
34 */
35
36SelectorParser.prototype.skip = function(len) {
37 this.str = this.str.substr(len);
38 this.pos += len;
39};
40
41/**
42 * Consume spaces.
43 */
44
45SelectorParser.prototype.skipSpaces = function() {
46 while (' ' == this.str[0]) this.skip(1);
47};
48
49/**
50 * Fetch next token.
51 *
52 * @return {String}
53 * @api private
54 */
55
56SelectorParser.prototype.advance = function() {
57 return this.root()
58 || this.relative()
59 || this.initial()
60 || this.escaped()
61 || this.parent()
62 || this.partial()
63 || this.char();
64};
65
66/**
67 * '/'
68 */
69
70SelectorParser.prototype.root = function() {
71 if (!this.pos && '/' == this.str[0]
72 && 'deep' != this.str.slice(1, 5)) {
73 this.nested = false;
74 this.skip(1);
75 }
76};
77
78/**
79 * '../'
80 */
81
82SelectorParser.prototype.relative = function(multi) {
83 if ((!this.pos || multi) && '../' == this.str.slice(0, 3)) {
84 this.nested = false;
85 this.skip(3);
86 while (this.relative(true)) this.level++;
87 if (!this.raw) {
88 var ret = this.stack[this.stack.length - this.level];
89 if (ret) {
90 return ret;
91 } else {
92 this.ignore = true;
93 }
94 }
95 }
96};
97
98/**
99 * '~/'
100 */
101
102SelectorParser.prototype.initial = function() {
103 if (!this.pos && '~' == this.str[0] && '/' == this.str[1]) {
104 this.nested = false;
105 this.skip(2);
106 return this.stack[0];
107 }
108};
109
110/**
111 * '\' ('&' | '^')
112 */
113
114SelectorParser.prototype.escaped = function() {
115 if ('\\' == this.str[0]) {
116 var char = this.str[1];
117 if ('&' == char || '^' == char) {
118 this.skip(2);
119 return char;
120 }
121 }
122};
123
124/**
125 * '&'
126 */
127
128SelectorParser.prototype.parent = function() {
129 if ('&' == this.str[0]) {
130 this.nested = false;
131
132 if (!this.pos && (!this.stack.length || this.raw)) {
133 var i = 0;
134 while (' ' == this.str[++i]) ;
135 if (~COMBINATORS.indexOf(this.str[i])) {
136 this.skip(i + 1);
137 return;
138 }
139 }
140
141 this.skip(1);
142 if (!this.raw)
143 return this.stack[this.stack.length - 1];
144 }
145};
146
147/**
148 * '^[' range ']'
149 */
150
151SelectorParser.prototype.partial = function() {
152 if ('^' == this.str[0] && '[' == this.str[1]) {
153 this.skip(2);
154 this.skipSpaces();
155 var ret = this.range();
156 this.skipSpaces();
157 if (']' != this.str[0]) return '^[';
158 this.nested = false;
159 this.skip(1);
160 if (ret) {
161 return ret;
162 } else {
163 this.ignore = true;
164 }
165 }
166};
167
168/**
169 * '-'? 0-9+
170 */
171
172SelectorParser.prototype.number = function() {
173 var i = 0, ret = '';
174 if ('-' == this.str[i])
175 ret += this.str[i++];
176
177 while (this.str.charCodeAt(i) >= 48
178 && this.str.charCodeAt(i) <= 57)
179 ret += this.str[i++];
180
181 if (ret) {
182 this.skip(i);
183 return Number(ret);
184 }
185};
186
187/**
188 * number ('..' number)?
189 */
190
191SelectorParser.prototype.range = function() {
192 var start = this.number()
193 , ret;
194
195 if ('..' == this.str.slice(0, 2)) {
196 this.skip(2);
197 var end = this.number()
198 , len = this.parts.length;
199
200 if (start < 0) start = len + start - 1;
201 if (end < 0) end = len + end - 1;
202
203 if (start > end) {
204 var tmp = start;
205 start = end;
206 end = tmp;
207 }
208
209 if (end < len - 1) {
210 ret = this.parts.slice(start, end + 1).map(function(part) {
211 var selector = new SelectorParser(part, this.stack, this.parts);
212 selector.raw = true;
213 return selector.parse();
214 }, this).map(function(selector) {
215 return (selector.nested ? ' ' : '') + selector.val;
216 }).join('').trim();
217 }
218 } else {
219 ret = this.stack[
220 start < 0 ? this.stack.length + start - 1 : start
221 ];
222 }
223
224 if (ret) {
225 return ret;
226 } else {
227 this.ignore = true;
228 }
229};
230
231/**
232 * .+
233 */
234
235SelectorParser.prototype.char = function() {
236 var char = this.str[0];
237 this.skip(1);
238 return char;
239};
240
241/**
242 * Parses the selector.
243 *
244 * @return {Object}
245 * @api private
246 */
247
248SelectorParser.prototype.parse = function() {
249 var val = '';
250 while (this.str.length) {
251 val += this.advance() || '';
252 if (this.ignore) {
253 val = '';
254 break;
255 }
256 }
257 return { val: val.trimRight(), nested: this.nested };
258};