|
1 | | -import AtRule from 'postcss/lib/at-rule'; |
2 | 1 | import Comment from 'postcss/lib/comment'; |
3 | 2 | import Parser from 'postcss/lib/parser'; |
4 | | - |
5 | | -import isMixinName from './tokenizer/is-mixin-name'; |
| 3 | +import isMixinName from './is-mixin-name'; |
6 | 4 | import lessTokenizer from './less-tokenize'; |
7 | 5 |
|
8 | | -const mixinRuleToken = 'mixinRule'; |
9 | | -const mixinParamToken = 'mixin-param'; |
10 | | -const atRuleToken = 'atrule'; |
11 | | -const defaultOptions = { |
12 | | - mixinsAsAtRules: false, |
13 | | - innerMixinsAsRules: false |
14 | | -}; |
15 | | - |
16 | 6 | export default class LessParser extends Parser { |
17 | | - constructor (input, opts) { |
18 | | - super(input); |
19 | | - |
20 | | - Object.assign(this, defaultOptions, opts); |
21 | | - } |
22 | | - |
23 | 7 | tokenize () { |
24 | 8 | this.tokens = lessTokenizer(this.input); |
25 | 9 | } |
@@ -50,98 +34,128 @@ export default class LessParser extends Parser { |
50 | 34 | } |
51 | 35 | } |
52 | 36 |
|
53 | | - rule (tokens) { |
54 | | - super.rule(tokens); |
55 | | - |
56 | | - const {selector} = this.current; |
| 37 | + /** |
| 38 | + * @description Create a Rule node |
| 39 | + * @param options {{start: number, params: Array}} |
| 40 | + */ |
| 41 | + createRule (options) { |
| 42 | + this.rule(this.tokens.slice(options.start, this.pos + 1)); |
| 43 | + this.raw(this.current, 'params', options.params); |
| 44 | + } |
| 45 | + |
| 46 | + /** |
| 47 | + * @description Create a Declaration |
| 48 | + * @param options {{start: number}} |
| 49 | + */ |
| 50 | + createDeclaration (options) { |
| 51 | + this.decl(this.tokens.slice(options.start, this.pos + 1)); |
| 52 | + } |
| 53 | + |
| 54 | + /** |
| 55 | + * @description Create a Rule block and close it, because this mixin doesn't have a body |
| 56 | + * @param options |
| 57 | + */ |
| 58 | + mixinWithoutBody (options) { |
| 59 | + this.createRule(options); |
57 | 60 |
|
58 | 61 | /** |
59 | | - * We should separate rules and mixins. According to LESS spec (http://lesscss.org/features/#mixins-feature) |
60 | | - * mixins can have the same syntax as inner blocks, so we will handle only mixins as functions, |
61 | | - * that contain symbol '('. |
| 62 | + * @description Mark this node |
| 63 | + * @type {boolean} |
62 | 64 | */ |
63 | | - if (isMixinName(selector) && selector.indexOf('(') > 0) { |
64 | | - // this.current is the 'rule' node created in super.rule() |
65 | | - this.current.name = tokens[0][1].slice(1); |
| 65 | + this.current.mixinWithoutBody = true; |
| 66 | + this.current.important = this.current.selector.indexOf('!important') >= 0; |
| 67 | + this.end(this.tokens[this.pos]); |
| 68 | + } |
66 | 69 |
|
67 | | - // parse inner mixin rule nodes as base rule node |
68 | | - if (this.innerMixinsAsRules && this.current.parent.type !== 'root') { |
69 | | - return; |
70 | | - } |
| 70 | + /* eslint-disable max-statements, complexity */ |
| 71 | + word () { |
| 72 | + let token = this.tokens[this.pos]; |
| 73 | + let end = false; |
| 74 | + let colon = false; |
| 75 | + let bracket = null; |
| 76 | + let brackets = 0; |
| 77 | + const start = this.pos; |
| 78 | + const isMixin = isMixinName(token[1]); |
| 79 | + const params = []; |
| 80 | + |
| 81 | + this.pos += 1; |
71 | 82 |
|
72 | | - if (this.mixinsAsAtRules === true) { |
73 | | - this.current.type = atRuleToken; |
74 | | - this.current.lessType = mixinRuleToken; |
75 | | - } else { |
76 | | - this.current.type = mixinRuleToken; |
77 | | - } |
| 83 | + while (this.pos < this.tokens.length) { |
| 84 | + token = this.tokens[this.pos]; |
| 85 | + const type = token[0]; |
78 | 86 |
|
79 | | - this.current.definition = true; |
80 | | - this.current.params = []; |
| 87 | + if (type === '(') { |
| 88 | + if (!bracket) { |
| 89 | + bracket = token; |
| 90 | + } |
81 | 91 |
|
82 | | - let blockStart = tokens.findIndex((x) => x[0] === '('); |
83 | | - const blockEnd = tokens.findIndex((x) => x[0] === ')'); |
| 92 | + brackets += 1; |
| 93 | + } else if (brackets === 0) { |
| 94 | + if (type === ';') { |
| 95 | + if (colon) { |
| 96 | + this.createDeclaration({start}); |
| 97 | + return; |
| 98 | + } |
| 99 | + |
| 100 | + if (isMixin) { |
| 101 | + this.mixinWithoutBody({start, params}); |
| 102 | + return; |
| 103 | + } |
84 | 104 |
|
85 | | - tokens.forEach((token) => { |
86 | | - if (token[0] !== mixinParamToken) { |
| 105 | + break; |
| 106 | + } else if (type === '{') { |
| 107 | + this.createRule({start, params}); |
87 | 108 | return; |
| 109 | + } else if (type === '}') { |
| 110 | + this.pos -= 1; |
| 111 | + end = true; |
| 112 | + break; |
| 113 | + } else if (type === ':') { |
| 114 | + colon = true; |
88 | 115 | } |
89 | | - |
90 | | - const index = tokens.indexOf(token); |
91 | | - const param = new AtRule(); |
92 | | - let pos = blockStart; |
93 | | - const prev = this.current.params[this.current.params.length - 1]; |
94 | | - let termToken = null; |
95 | | - |
96 | | - param.name = token[1].slice(1); |
97 | | - param.lessType = mixinParamToken; |
98 | | - param.source = { |
99 | | - start: {line: token[2], column: token[3]}, |
100 | | - input: this.input |
101 | | - }; |
102 | | - param.raws = {before: '', after: '', between: ''}; |
103 | | - |
104 | | - if (token[6] && token[6] === 'rest-variables') { |
105 | | - param.restVariables = true; |
| 116 | + } else if (type === ')') { |
| 117 | + brackets -= 1; |
| 118 | + if (brackets === 0) { |
| 119 | + bracket = null; |
106 | 120 | } |
| 121 | + } |
107 | 122 |
|
108 | | - while (pos < blockEnd) { |
109 | | - termToken = tokens[pos]; |
110 | | - |
111 | | - if (termToken[0] === ';') { |
112 | | - param.source.end = {line: termToken[2], column: termToken[3]}; |
113 | | - param.raws.semicolon = true; |
114 | | - blockStart = pos + 1; |
115 | | - break; |
116 | | - } else if (termToken[0] === 'space') { |
117 | | - param.raws.before += termToken[1]; |
118 | | - |
119 | | - if (prev) { |
120 | | - prev.raws.after += termToken[1]; |
121 | | - } |
122 | | - } |
| 123 | + if (brackets || type === 'brackets' || params[0]) { |
| 124 | + params.push(token); |
| 125 | + } |
123 | 126 |
|
124 | | - pos += 1; |
125 | | - } |
| 127 | + this.pos += 1; |
| 128 | + } |
126 | 129 |
|
127 | | - // this is the last param in the block |
128 | | - if (pos === blockEnd) { |
129 | | - pos = index; |
130 | | - while (pos < blockEnd) { |
131 | | - termToken = tokens[pos]; |
| 130 | + if (this.pos === this.tokens.length) { |
| 131 | + this.pos -= 1; |
| 132 | + end = true; |
| 133 | + } |
132 | 134 |
|
133 | | - if (termToken[0] === 'space') { |
134 | | - param.raws.after += termToken[1]; |
135 | | - } |
| 135 | + if (brackets > 0) { |
| 136 | + this.unclosedBracket(bracket); |
| 137 | + } |
136 | 138 |
|
137 | | - pos += 1; |
| 139 | + if (end) { |
| 140 | + if (colon) { |
| 141 | + while (this.pos > start) { |
| 142 | + token = this.tokens[this.pos][0]; |
| 143 | + if (token !== 'space' && token !== 'comment') { |
| 144 | + break; |
138 | 145 | } |
139 | 146 |
|
140 | | - param.source.end = {line: token[4], column: token[5]}; |
| 147 | + this.pos -= 1; |
141 | 148 | } |
142 | 149 |
|
143 | | - this.current.params.push(param); |
144 | | - }); |
| 150 | + this.createDeclaration({start}); |
| 151 | + return; |
| 152 | + } else if (isMixin) { |
| 153 | + this.mixinWithoutBody({start, params}); |
| 154 | + return; |
| 155 | + } |
145 | 156 | } |
| 157 | + |
| 158 | + this.unknownWord(start); |
146 | 159 | } |
| 160 | + /* eslint-enable max-statements, complexity */ |
147 | 161 | } |
0 commit comments