1 |
|
2 |
|
3 | const Comment = require('postcss/lib/comment');
|
4 | const Parser = require('postcss/lib/parser');
|
5 |
|
6 | const { isInlineComment } = require('./nodes/inline-comment');
|
7 | const { interpolation } = require('./nodes/interpolation');
|
8 | const { isMixinToken } = require('./nodes/mixin');
|
9 | const importNode = require('./nodes/import');
|
10 | const variableNode = require('./nodes/variable');
|
11 |
|
12 | const importantPattern = /(!\s*important)$/i;
|
13 |
|
14 | module.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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
192 |
|
193 |
|
194 | const [first] = tokens;
|
195 |
|
196 |
|
197 | if (tokens[0][1] === 'each' && tokens[1][0] === '(') {
|
198 | this.each(tokens);
|
199 | return;
|
200 | }
|
201 |
|
202 |
|
203 | if (isMixinToken(first)) {
|
204 | this.mixin(tokens);
|
205 | return;
|
206 | }
|
207 |
|
208 | super.unknownWord(tokens);
|
209 | }
|
210 | };
|