UNPKG

5.95 kBJavaScriptView Raw
1/* eslint no-param-reassign: off */
2
3const Comment = require('postcss/lib/comment');
4const Parser = require('postcss/lib/parser');
5
6const { isInlineComment } = require('./nodes/inline-comment');
7const { interpolation } = require('./nodes/interpolation');
8const { isMixinToken } = require('./nodes/mixin');
9const importNode = require('./nodes/import');
10const variableNode = require('./nodes/variable');
11
12const importantPattern = /(!\s*important)$/i;
13
14module.exports = class LessParser extends Parser {
15 constructor(...args) {
16 super(...args);
17
18 this.lastNode = null;
19 }
20
21 atrule(token) {
22 if (interpolation.bind(this)(token)) {
23 return;
24 }
25
26 super.atrule(token);
27 importNode(this.lastNode);
28 variableNode(this.lastNode);
29 }
30
31 decl(...args) {
32 super.decl(...args);
33
34 // #123: add `extend` decorator to nodes
35 const extendPattern = /extend\(.+\)/i;
36
37 if (extendPattern.test(this.lastNode.value)) {
38 this.lastNode.extend = true;
39 }
40 }
41
42 each(tokens) {
43 // prepend a space so the `name` will be parsed correctly
44 tokens[0][1] = ` ${tokens[0][1]}`;
45
46 const firstParenIndex = tokens.findIndex((t) => t[0] === '(');
47 const lastParen = tokens.reverse().find((t) => t[0] === ')');
48 const lastParenIndex = tokens.reverse().indexOf(lastParen);
49 const paramTokens = tokens.splice(firstParenIndex, lastParenIndex);
50 const params = paramTokens.map((t) => t[1]).join('');
51
52 for (const token of tokens.reverse()) {
53 this.tokenizer.back(token);
54 }
55
56 this.atrule(this.tokenizer.nextToken());
57 this.lastNode.function = true;
58 this.lastNode.params = params;
59 }
60
61 init(node, line, column) {
62 super.init(node, line, column);
63 this.lastNode = node;
64 }
65
66 inlineComment(token) {
67 const node = new Comment();
68 const text = token[1].slice(2);
69
70 this.init(node, token[2]);
71 node.source.end = this.getPosition(token[3] || token[2]);
72 node.inline = true;
73 node.raws.begin = '//';
74
75 if (/^\s*$/.test(text)) {
76 node.text = '';
77 node.raws.left = text;
78 node.raws.right = '';
79 } else {
80 const match = text.match(/^(\s*)([^]*[^\s])(\s*)$/);
81 [, node.raws.left, node.text, node.raws.right] = match;
82 }
83 }
84
85 mixin(tokens) {
86 const [first] = tokens;
87 const identifier = first[1].slice(0, 1);
88 const bracketsIndex = tokens.findIndex((t) => t[0] === 'brackets');
89 const firstParenIndex = tokens.findIndex((t) => t[0] === '(');
90 let important = '';
91
92 // fix for #86. if rulesets are mixin params, they need to be converted to a brackets token
93 if ((bracketsIndex < 0 || bracketsIndex > 3) && firstParenIndex > 0) {
94 const lastParenIndex = tokens.reduce((last, t, i) => (t[0] === ')' ? i : last));
95
96 const contents = tokens.slice(firstParenIndex, lastParenIndex + firstParenIndex);
97 const brackets = contents.map((t) => t[1]).join('');
98 const [paren] = tokens.slice(firstParenIndex);
99 const start = [paren[2], paren[3]];
100 const [last] = tokens.slice(lastParenIndex, lastParenIndex + 1);
101 const end = [last[2], last[3]];
102 const newToken = ['brackets', brackets].concat(start, end);
103
104 const tokensBefore = tokens.slice(0, firstParenIndex);
105 const tokensAfter = tokens.slice(lastParenIndex + 1);
106 tokens = tokensBefore;
107 tokens.push(newToken);
108 tokens = tokens.concat(tokensAfter);
109 }
110
111 const importantTokens = [];
112
113 for (const token of tokens) {
114 if (token[1] === '!' || importantTokens.length) {
115 importantTokens.push(token);
116 }
117
118 if (token[1] === 'important') {
119 break;
120 }
121 }
122
123 if (importantTokens.length) {
124 const [bangToken] = importantTokens;
125 const bangIndex = tokens.indexOf(bangToken);
126 const last = importantTokens[importantTokens.length - 1];
127 const start = [bangToken[2], bangToken[3]];
128 const end = [last[4], last[5]];
129 const combined = importantTokens.map((t) => t[1]).join('');
130 const newToken = ['word', combined].concat(start, end);
131 tokens.splice(bangIndex, importantTokens.length, newToken);
132 }
133
134 const importantIndex = tokens.findIndex((t) => importantPattern.test(t[1]));
135
136 if (importantIndex > 0) {
137 [, important] = tokens[importantIndex];
138 tokens.splice(importantIndex, 1);
139 }
140
141 for (const token of tokens.reverse()) {
142 this.tokenizer.back(token);
143 }
144
145 this.atrule(this.tokenizer.nextToken());
146 this.lastNode.mixin = true;
147 this.lastNode.raws.identifier = identifier;
148
149 if (important) {
150 this.lastNode.important = true;
151 this.lastNode.raws.important = important;
152 }
153 }
154
155 other(token) {
156 if (!isInlineComment.bind(this)(token)) {
157 super.other(token);
158 }
159 }
160
161 rule(tokens) {
162 const last = tokens[tokens.length - 1];
163 const prev = tokens[tokens.length - 2];
164
165 if (prev[0] === 'at-word' && last[0] === '{') {
166 this.tokenizer.back(last);
167 if (interpolation.bind(this)(prev)) {
168 const newToken = this.tokenizer.nextToken();
169
170 tokens = tokens.slice(0, tokens.length - 2).concat([newToken]);
171
172 for (const tokn of tokens.reverse()) {
173 this.tokenizer.back(tokn);
174 }
175
176 return;
177 }
178 }
179
180 super.rule(tokens);
181
182 // #123: add `extend` decorator to nodes
183 const extendPattern = /:extend\(.+\)/i;
184
185 if (extendPattern.test(this.lastNode.selector)) {
186 this.lastNode.extend = true;
187 }
188 }
189
190 unknownWord(tokens) {
191 // NOTE: keep commented for examining unknown structures
192 // console.log('unknown', tokens);
193
194 const [first] = tokens;
195
196 // #121 support `each` - http://lesscss.org/functions/#list-functions-each
197 if (tokens[0][1] === 'each' && tokens[1][0] === '(') {
198 this.each(tokens);
199 return;
200 }
201
202 // TODO: move this into a util function/file
203 if (isMixinToken(first)) {
204 this.mixin(tokens);
205 return;
206 }
207
208 super.unknownWord(tokens);
209 }
210};