OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * @constructor | |
7 * @implements {FormatterWorker.FormatterWorkerContentParser} | 6 * @implements {FormatterWorker.FormatterWorkerContentParser} |
8 */ | 7 * @unrestricted |
9 Gonzales.SCSSParser = function() | 8 */ |
10 { | 9 Gonzales.SCSSParser = class { |
11 } | 10 /** |
12 | 11 * @override |
13 Gonzales.SCSSParser.prototype = { | 12 * @param {string} content |
14 /** | 13 * @return {!Array<!Gonzales.SCSSParser.Rule>} |
15 * @override | 14 */ |
16 * @param {string} content | 15 parse(content) { |
17 * @return {!Array<!Gonzales.SCSSParser.Rule>} | 16 var ast = null; |
18 */ | 17 try { |
19 parse: function(content) | 18 ast = gonzales.parse(content, {syntax: 'scss'}); |
20 { | 19 } catch (e) { |
21 var ast = null; | 20 return []; |
22 try { | 21 } |
23 ast = gonzales.parse(content, {syntax: "scss"}); | 22 |
24 } catch (e) { | 23 /** @type {!{properties: !Array<!Gonzales.Node>, node: !Gonzales.Node}} */ |
25 return []; | 24 var rootBlock = {properties: [], node: ast}; |
26 } | 25 /** @type {!Array<!{properties: !Array<!Gonzales.Node>, node: !Gonzales.Node
}>} */ |
27 | 26 var blocks = [rootBlock]; |
28 /** @type {!{properties: !Array<!Gonzales.Node>, node: !Gonzales.Node}}
*/ | 27 ast.selectors = []; |
29 var rootBlock = { | 28 Gonzales.SCSSParser.extractNodes(ast, blocks, rootBlock); |
30 properties: [], | 29 |
31 node: ast | 30 var rules = []; |
32 }; | 31 for (var block of blocks) |
33 /** @type {!Array<!{properties: !Array<!Gonzales.Node>, node: !Gonzales.
Node}>} */ | 32 this._handleBlock(block, rules); |
34 var blocks = [rootBlock]; | 33 return rules; |
35 ast.selectors = []; | 34 } |
36 Gonzales.SCSSParser.extractNodes(ast, blocks, rootBlock); | 35 |
37 | 36 /** |
38 var rules = []; | 37 * @param {!{node: !Gonzales.Node, properties: !Array<!Gonzales.Node>}} block |
39 for (var block of blocks) | 38 * @param {!Array<!Gonzales.SCSSParser.Rule>} output |
40 this._handleBlock(block, rules); | 39 */ |
41 return rules; | 40 _handleBlock(block, output) { |
42 }, | 41 var selectors = block.node.selectors.map(Gonzales.SCSSParser.rangeFromNode); |
43 | 42 var properties = []; |
44 /** | 43 var styleRange = Gonzales.SCSSParser.rangeFromNode(block.node); |
45 * @param {!{node: !Gonzales.Node, properties: !Array<!Gonzales.Node>}} bloc
k | 44 styleRange.startColumn += 1; |
46 * @param {!Array<!Gonzales.SCSSParser.Rule>} output | 45 styleRange.endColumn -= 1; |
47 */ | 46 for (var node of block.properties) { |
48 _handleBlock: function(block, output) | 47 if (node.type === 'declaration') |
49 { | 48 this._handleDeclaration(node, properties); |
50 var selectors = block.node.selectors.map(Gonzales.SCSSParser.rangeFromNo
de); | 49 else if (node.type === 'include') |
51 var properties = []; | 50 this._handleInclude(node, properties); |
52 var styleRange = Gonzales.SCSSParser.rangeFromNode(block.node); | 51 else if (node.type === 'multilineComment' && node.start.line === node.end.
line) |
53 styleRange.startColumn += 1; | 52 this._handleComment(node, properties); |
54 styleRange.endColumn -= 1; | 53 } |
55 for (var node of block.properties) { | 54 if (!selectors.length && !properties.length) |
56 if (node.type === "declaration") | 55 return; |
57 this._handleDeclaration(node, properties); | 56 var rule = new Gonzales.SCSSParser.Rule(selectors, properties, styleRange); |
58 else if (node.type === "include") | 57 output.push(rule); |
59 this._handleInclude(node, properties); | 58 } |
60 else if (node.type === "multilineComment" && node.start.line === nod
e.end.line) | 59 |
61 this._handleComment(node, properties); | 60 /** |
62 } | 61 * @param {!Gonzales.Node} node |
63 if (!selectors.length && !properties.length) | 62 * @param {!Array<!Gonzales.SCSSParser.Property>} output |
64 return; | 63 */ |
65 var rule = new Gonzales.SCSSParser.Rule(selectors, properties, styleRang
e); | 64 _handleDeclaration(node, output) { |
66 output.push(rule); | 65 var propertyNode = node.content.find(node => node.type === 'property'); |
67 }, | 66 var valueNode = node.content.find(node => node.type === 'value'); |
68 | 67 if (!propertyNode || !valueNode) |
69 /** | 68 return; |
70 * @param {!Gonzales.Node} node | 69 |
71 * @param {!Array<!Gonzales.SCSSParser.Property>} output | 70 var nameRange = Gonzales.SCSSParser.rangeFromNode(propertyNode); |
72 */ | 71 var valueRange = Gonzales.SCSSParser.rangeFromNode(valueNode); |
73 _handleDeclaration: function(node, output) | 72 var range = /** @type {!Gonzales.TextRange} */ (node.declarationRange); |
74 { | 73 |
75 var propertyNode = node.content.find(node => node.type === "property"); | 74 var property = new Gonzales.SCSSParser.Property(range, nameRange, valueRange
, false); |
76 var valueNode = node.content.find(node => node.type === "value"); | 75 output.push(property); |
77 if (!propertyNode || !valueNode) | 76 } |
78 return; | 77 |
79 | 78 /** |
80 var nameRange = Gonzales.SCSSParser.rangeFromNode(propertyNode); | 79 * @param {!Gonzales.Node} node |
81 var valueRange = Gonzales.SCSSParser.rangeFromNode(valueNode); | 80 * @param {!Array<!Gonzales.SCSSParser.Property>} output |
82 var range = /** @type {!Common.TextRange} */(node.declarationRange); | 81 */ |
83 | 82 _handleInclude(node, output) { |
84 var property = new Gonzales.SCSSParser.Property(range, nameRange, valueR
ange, false); | 83 var mixinName = node.content.find(node => node.type === 'ident'); |
85 output.push(property); | 84 if (!mixinName) |
86 }, | 85 return; |
87 | 86 var nameRange = Gonzales.SCSSParser.rangeFromNode(mixinName); |
88 /** | 87 var mixinArguments = node.content.find(node => node.type === 'arguments'); |
89 * @param {!Gonzales.Node} node | 88 if (!mixinArguments) |
90 * @param {!Array<!Gonzales.SCSSParser.Property>} output | 89 return; |
91 */ | 90 var parameters = mixinArguments.content.filter(node => node.type !== 'delimi
ter' && node.type !== 'space'); |
92 _handleInclude: function(node, output) | 91 for (var parameter of parameters) { |
93 { | 92 var range = Gonzales.SCSSParser.rangeFromNode(node); |
94 var mixinName = node.content.find(node => node.type === "ident"); | 93 var valueRange = Gonzales.SCSSParser.rangeFromNode(parameter); |
95 if (!mixinName) | 94 var property = new Gonzales.SCSSParser.Property(range, nameRange, valueRan
ge, false); |
96 return; | 95 output.push(property); |
97 var nameRange = Gonzales.SCSSParser.rangeFromNode(mixinName); | 96 } |
98 var mixinArguments = node.content.find(node => node.type === "arguments"
); | 97 } |
99 if (!mixinArguments) | 98 |
100 return; | 99 /** |
101 var parameters = mixinArguments.content.filter(node => node.type !== "de
limiter" && node.type !== "space"); | 100 * @param {!Gonzales.Node} node |
102 for (var parameter of parameters) { | 101 * @param {!Array<!Gonzales.SCSSParser.Property>} output |
103 var range = Gonzales.SCSSParser.rangeFromNode(node); | 102 */ |
104 var valueRange = Gonzales.SCSSParser.rangeFromNode(parameter); | 103 _handleComment(node, output) { |
105 var property = new Gonzales.SCSSParser.Property(range, nameRange, va
lueRange, false); | 104 if (node.start.line !== node.end.line) |
106 output.push(property); | 105 return; |
107 } | 106 var innerText = /** @type {string} */ (node.content); |
108 }, | 107 var innerResult = this.parse(innerText); |
109 | 108 if (innerResult.length !== 1 || innerResult[0].properties.length !== 1) |
110 /** | 109 return; |
111 * @param {!Gonzales.Node} node | 110 var property = innerResult[0].properties[0]; |
112 * @param {!Array<!Gonzales.SCSSParser.Property>} output | 111 var disabledProperty = property.rebaseInsideOneLineComment(node); |
113 */ | 112 output.push(disabledProperty); |
114 _handleComment: function(node, output) | 113 } |
115 { | 114 }; |
116 if (node.start.line !== node.end.line) | |
117 return; | |
118 var innerText = /** @type {string} */(node.content); | |
119 var innerResult = this.parse(innerText); | |
120 if (innerResult.length !== 1 || innerResult[0].properties.length !== 1) | |
121 return; | |
122 var property = innerResult[0].properties[0]; | |
123 var disabledProperty = property.rebaseInsideOneLineComment(node); | |
124 output.push(disabledProperty); | |
125 }, | |
126 } | |
127 | 115 |
128 /** | 116 /** |
129 * @param {!Gonzales.Node} node | 117 * @param {!Gonzales.Node} node |
130 * @return {!Common.TextRange} | 118 * @return {!Gonzales.TextRange} |
131 */ | 119 */ |
132 Gonzales.SCSSParser.rangeFromNode = function(node) | 120 Gonzales.SCSSParser.rangeFromNode = function(node) { |
133 { | 121 return new Gonzales.TextRange(node.start.line - 1, node.start.column - 1, node
.end.line - 1, node.end.column); |
134 return new Common.TextRange(node.start.line - 1, node.start.column - 1, node
.end.line - 1, node.end.column); | 122 }; |
135 } | 123 |
136 | 124 /** |
137 /** | 125 * @unrestricted |
138 * @constructor | 126 */ |
139 * @param {!Common.TextRange} range | 127 Gonzales.SCSSParser.Property = class { |
140 * @param {!Common.TextRange} nameRange | 128 /** |
141 * @param {!Common.TextRange} valueRange | 129 * @param {!Gonzales.TextRange} range |
142 * @param {boolean} disabled | 130 * @param {!Gonzales.TextRange} nameRange |
143 */ | 131 * @param {!Gonzales.TextRange} valueRange |
144 Gonzales.SCSSParser.Property = function(range, nameRange, valueRange, disabled) | 132 * @param {boolean} disabled |
145 { | 133 */ |
| 134 constructor(range, nameRange, valueRange, disabled) { |
146 this.range = range; | 135 this.range = range; |
147 this.name = nameRange; | 136 this.name = nameRange; |
148 this.value = valueRange; | 137 this.value = valueRange; |
149 this.disabled = disabled; | 138 this.disabled = disabled; |
150 } | 139 } |
151 | 140 |
152 Gonzales.SCSSParser.Property.prototype = { | 141 /** |
| 142 * @param {!Gonzales.Node} commentNode |
| 143 * @return {!Gonzales.SCSSParser.Property} |
| 144 */ |
| 145 rebaseInsideOneLineComment(commentNode) { |
| 146 var lineOffset = commentNode.start.line - 1; |
| 147 // Account for the "/*". |
| 148 var columnOffset = commentNode.start.column - 1 + 2; |
| 149 var range = Gonzales.SCSSParser.rangeFromNode(commentNode); |
| 150 var name = rebaseRange(this.name, lineOffset, columnOffset); |
| 151 var value = rebaseRange(this.value, lineOffset, columnOffset); |
| 152 return new Gonzales.SCSSParser.Property(range, name, value, true); |
| 153 |
153 /** | 154 /** |
154 * @param {!Gonzales.Node} commentNode | 155 * @param {!Gonzales.TextRange} range |
155 * @return {!Gonzales.SCSSParser.Property} | 156 * @param {number} lineOffset |
| 157 * @param {number} columnOffset |
| 158 * @return {!Gonzales.TextRange} |
156 */ | 159 */ |
157 rebaseInsideOneLineComment: function(commentNode) | 160 function rebaseRange(range, lineOffset, columnOffset) { |
158 { | 161 return new Gonzales.TextRange( |
159 var lineOffset = commentNode.start.line - 1; | 162 range.startLine + lineOffset, range.startColumn + columnOffset, range.
endLine + lineOffset, |
160 // Account for the "/*". | 163 range.endColumn + columnOffset); |
161 var columnOffset = commentNode.start.column - 1 + 2; | 164 } |
162 var range = Gonzales.SCSSParser.rangeFromNode(commentNode); | 165 } |
163 var name = rebaseRange(this.name, lineOffset, columnOffset); | 166 }; |
164 var value = rebaseRange(this.value, lineOffset, columnOffset); | 167 |
165 return new Gonzales.SCSSParser.Property(range, name, value, true); | 168 /** |
166 | 169 * @unrestricted |
167 /** | 170 */ |
168 * @param {!Common.TextRange} range | 171 Gonzales.SCSSParser.Rule = class { |
169 * @param {number} lineOffset | 172 /** |
170 * @param {number} columnOffset | 173 * @param {!Array<!Gonzales.TextRange>} selectors |
171 * @return {!Common.TextRange} | 174 * @param {!Array<!Gonzales.SCSSParser.Property>} properties |
172 */ | 175 * @param {!Gonzales.TextRange} styleRange |
173 function rebaseRange(range, lineOffset, columnOffset) | 176 */ |
174 { | 177 constructor(selectors, properties, styleRange) { |
175 return new Common.TextRange(range.startLine + lineOffset, range.star
tColumn + columnOffset, range.endLine + lineOffset, range.endColumn + columnOffs
et); | |
176 } | |
177 } | |
178 } | |
179 | |
180 /** | |
181 * @constructor | |
182 * @param {!Array<!Common.TextRange>} selectors | |
183 * @param {!Array<!Gonzales.SCSSParser.Property>} properties | |
184 * @param {!Common.TextRange} styleRange | |
185 */ | |
186 Gonzales.SCSSParser.Rule = function(selectors, properties, styleRange) | |
187 { | |
188 this.selectors = selectors; | 178 this.selectors = selectors; |
189 this.properties = properties; | 179 this.properties = properties; |
190 this.styleRange = styleRange; | 180 this.styleRange = styleRange; |
191 } | 181 } |
| 182 }; |
192 | 183 |
193 /** | 184 /** |
194 * @param {!Gonzales.Node} node | 185 * @param {!Gonzales.Node} node |
195 * @param {!Array<{node: !Gonzales.Node, properties: !Array<!Gonzales.Node>}>} b
locks | 186 * @param {!Array<{node: !Gonzales.Node, properties: !Array<!Gonzales.Node>}>} b
locks |
196 * @param {!{node: !Gonzales.Node, properties: !Array<!Gonzales.Node>}} lastBloc
k | 187 * @param {!{node: !Gonzales.Node, properties: !Array<!Gonzales.Node>}} lastBloc
k |
197 */ | 188 */ |
198 Gonzales.SCSSParser.extractNodes = function(node, blocks, lastBlock) | 189 Gonzales.SCSSParser.extractNodes = function(node, blocks, lastBlock) { |
199 { | 190 if (!Array.isArray(node.content)) |
200 if (!Array.isArray(node.content)) | 191 return; |
201 return; | 192 if (node.type === 'block') { |
202 if (node.type === "block") { | 193 lastBlock = {node: node, properties: []}; |
203 lastBlock = { | 194 blocks.push(lastBlock); |
204 node: node, | 195 } |
205 properties: [] | 196 var lastDeclaration = null; |
206 }; | 197 var selectors = []; |
207 blocks.push(lastBlock); | 198 for (var i = 0; i < node.content.length; ++i) { |
208 } | 199 var child = node.content[i]; |
209 var lastDeclaration = null; | 200 if (child.type === 'declarationDelimiter' && lastDeclaration) { |
210 var selectors = []; | 201 lastDeclaration.declarationRange.endLine = child.end.line - 1; |
211 for (var i = 0; i < node.content.length; ++i) { | 202 lastDeclaration.declarationRange.endColumn = child.end.column; |
212 var child = node.content[i]; | 203 lastDeclaration = null; |
213 if (child.type === "declarationDelimiter" && lastDeclaration) { | 204 } else if (child.type === 'selector') { |
214 lastDeclaration.declarationRange.endLine = child.end.line - 1; | 205 selectors.push(child); |
215 lastDeclaration.declarationRange.endColumn = child.end.column; | 206 } else if (child.type === 'block') { |
216 lastDeclaration = null; | 207 child.selectors = selectors; |
217 } else if (child.type === "selector") { | 208 selectors = []; |
218 selectors.push(child); | 209 } |
219 } else if (child.type === "block") { | 210 if (child.type === 'include' || child.type === 'declaration' || child.type =
== 'multilineComment') |
220 child.selectors = selectors; | 211 lastBlock.properties.push(child); |
221 selectors = []; | 212 if (child.type === 'declaration') { |
222 } | 213 lastDeclaration = child; |
223 if (child.type === "include" || child.type === "declaration" || child.ty
pe === "multilineComment") | 214 const line = child.start.line - 1; |
224 lastBlock.properties.push(child); | 215 const column = child.start.column - 1; |
225 if (child.type === "declaration") { | 216 lastDeclaration.declarationRange = new Gonzales.TextRange(line, column, li
ne, column); |
226 lastDeclaration = child; | 217 } |
227 lastDeclaration.declarationRange = Common.TextRange.createFromLocati
on(child.start.line - 1, child.start.column - 1); | 218 Gonzales.SCSSParser.extractNodes(child, blocks, lastBlock); |
228 } | 219 } |
229 Gonzales.SCSSParser.extractNodes(child, blocks, lastBlock); | 220 if (lastDeclaration) { |
230 } | 221 lastDeclaration.declarationRange.endLine = node.end.line - 1; |
231 if (lastDeclaration) { | 222 lastDeclaration.declarationRange.endColumn = node.end.column - 1; |
232 lastDeclaration.declarationRange.endLine = node.end.line - 1; | 223 } |
233 lastDeclaration.declarationRange.endColumn = node.end.column - 1; | 224 }; |
234 } | 225 |
235 } | 226 /** |
| 227 * @unrestricted |
| 228 */ |
| 229 Gonzales.TextRange = class { |
| 230 /** |
| 231 * @param {number} startLine |
| 232 * @param {number} startColumn |
| 233 * @param {number} endLine |
| 234 * @param {number} endColumn |
| 235 */ |
| 236 constructor(startLine, startColumn, endLine, endColumn) { |
| 237 this.startLine = startLine; |
| 238 this.startColumn = startColumn; |
| 239 this.endLine = endLine; |
| 240 this.endColumn = endColumn; |
| 241 } |
| 242 }; |
OLD | NEW |