Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 WebInspector.SASSSupport = {} | |
| 6 | |
| 7 /** | |
| 8 * @param {string} url | |
| 9 * @param {string} text | |
| 10 * @param {!WebInspector.TokenizerFactory} tokenizerFactory | |
| 11 * @return {!WebInspector.SASSSupport.AST} | |
| 12 */ | |
| 13 WebInspector.SASSSupport.parseSCSS = function(url, text, tokenizerFactory) | |
| 14 { | |
| 15 var result = WebInspector.SASSSupport._innerParseSCSS(text, tokenizerFactory ); | |
| 16 | |
| 17 var rules = [ | |
| 18 new WebInspector.SASSSupport.Rule("variables", result.variables), | |
| 19 new WebInspector.SASSSupport.Rule("properties", result.properties), | |
| 20 new WebInspector.SASSSupport.Rule("mixins", result.mixins) | |
| 21 ]; | |
| 22 | |
| 23 return new WebInspector.SASSSupport.AST(url, text, rules); | |
| 24 } | |
| 25 | |
| 26 /** @enum {string} */ | |
| 27 WebInspector.SASSSupport.SCSSParserStates = { | |
| 28 Initial: "Initial", | |
| 29 PropertyName: "PropertyName", | |
| 30 PropertyValue: "PropertyValue", | |
| 31 VariableName: "VariableName", | |
| 32 VariableValue: "VariableValue", | |
| 33 MixinName: "MixinName", | |
| 34 MixinValue: "MixinValue", | |
| 35 Media: "Media", | |
| 36 } | |
| 37 | |
| 38 /** | |
| 39 * @param {string} text | |
| 40 * @param {!WebInspector.TokenizerFactory} tokenizerFactory | |
| 41 * @return {!{variables: !Array<!WebInspector.SASSSupport.Property>, properties: !Array<!WebInspector.SASSSupport.Property>, mixins: !Array<!WebInspector.SASSSu pport.Property>}} | |
| 42 */ | |
| 43 WebInspector.SASSSupport._innerParseSCSS = function(text, tokenizerFactory) | |
| 44 { | |
| 45 var lines = text.split("\n"); | |
| 46 var properties = []; | |
| 47 var variables = []; | |
| 48 var mixins = []; | |
| 49 | |
| 50 var States = WebInspector.SASSSupport.SCSSParserStates; | |
| 51 var state = States.Initial; | |
| 52 var propertyName, propertyValue; | |
| 53 var variableName, variableValue; | |
| 54 var mixinName, mixinValue; | |
| 55 var UndefTokenType = {}; | |
| 56 | |
| 57 /** | |
| 58 * @param {string} tokenValue | |
| 59 * @param {?string} tokenTypes | |
| 60 * @param {number} column | |
| 61 * @param {number} newColumn | |
| 62 */ | |
| 63 function processToken(tokenValue, tokenTypes, column, newColumn) | |
| 64 { | |
| 65 var tokenType = tokenTypes ? tokenTypes.split(" ").keySet() : UndefToken Type; | |
| 66 switch (state) { | |
| 67 case States.Initial: | |
| 68 if (tokenType["css-variable-2"]) { | |
| 69 variableName = new WebInspector.SASSSupport.TextNode(tokenValue, new WebInspector.TextRange(lineNumber, column, lineNumber, newColumn)); | |
| 70 state = States.VariableName; | |
| 71 } else if (tokenType["css-property"] || tokenType["css-meta"]) { | |
| 72 propertyName = new WebInspector.SASSSupport.TextNode(tokenValue, new WebInspector.TextRange(lineNumber, column, lineNumber, newColumn)); | |
| 73 state = States.PropertyName; | |
| 74 } else if (tokenType["css-def"] && tokenValue === "@include") { | |
| 75 mixinName = new WebInspector.SASSSupport.TextNode(tokenValue, ne w WebInspector.TextRange(lineNumber, column, lineNumber, newColumn)); | |
| 76 state = States.MixinName; | |
| 77 } else if (tokenType["css-comment"]) { | |
| 78 // The |processToken| is called per-line, so no token spans more then one line. | |
|
pfeldman
2015/12/09 03:50:59
huh?
| |
| 79 // Support only a one-line comments. | |
| 80 if (tokenValue.substring(0, 2) !== "/*" || tokenValue.substring( tokenValue.length - 2) !== "*/") | |
| 81 break; | |
| 82 var uncommentedText = tokenValue.substring(2, tokenValue.length - 2); | |
| 83 var fakeRule = "a{\n" + uncommentedText + "}"; | |
| 84 var result = WebInspector.SASSSupport._innerParseSCSS(fakeRule, tokenizerFactory); | |
| 85 if (result.properties.length === 1 && result.variables.length == = 0 && result.mixins.length === 0) { | |
| 86 var disabledProperty = result.properties[0]; | |
| 87 // We should offset property to current coordinates. | |
| 88 var offset = column + 2; | |
| 89 disabledProperty.disabled = true; | |
| 90 disabledProperty.range = WebInspector.TextRange.createFromLo cation(lineNumber, column); | |
| 91 disabledProperty.range.endColumn = newColumn; | |
| 92 disabledProperty.name.range.startLine = lineNumber; | |
| 93 disabledProperty.name.range.startColumn += offset; | |
| 94 disabledProperty.name.range.endLine = lineNumber; | |
| 95 disabledProperty.name.range.endColumn += offset; | |
| 96 disabledProperty.value.range.startLine = lineNumber; | |
| 97 disabledProperty.value.range.startColumn += offset; | |
| 98 disabledProperty.value.range.endLine = lineNumber; | |
| 99 disabledProperty.value.range.endColumn += offset; | |
| 100 properties.push(disabledProperty); | |
| 101 } | |
| 102 } else if (tokenType["css-def"] && tokenValue === "@media") { | |
| 103 state = States.Media; | |
| 104 } | |
| 105 break; | |
| 106 case States.VariableName: | |
| 107 if (tokenValue === ")" && tokenType === UndefTokenType) { | |
| 108 state = States.Initial; | |
| 109 } else if (tokenValue === ":" && tokenType === UndefTokenType) { | |
| 110 state = States.VariableValue; | |
| 111 variableValue = new WebInspector.SASSSupport.TextNode("", WebIns pector.TextRange.createFromLocation(lineNumber, newColumn)); | |
| 112 } else if (tokenType !== UndefTokenType) { | |
| 113 state = States.Initial; | |
| 114 } | |
| 115 break; | |
| 116 case States.VariableValue: | |
| 117 if (tokenValue === ";" && tokenType === UndefTokenType) { | |
| 118 variableValue.range.endLine = lineNumber; | |
| 119 variableValue.range.endColumn = column; | |
| 120 var variable = new WebInspector.SASSSupport.Property(variableNam e, variableValue, variableName.range.clone(), false); | |
| 121 variable.range.endLine = lineNumber; | |
| 122 variable.range.endColumn = newColumn; | |
| 123 variables.push(variable); | |
| 124 state = States.Initial; | |
| 125 } else { | |
| 126 variableValue.text += tokenValue; | |
| 127 } | |
| 128 break; | |
| 129 case States.PropertyName: | |
| 130 if (tokenValue === ":" && tokenType === UndefTokenType) { | |
| 131 state = States.PropertyValue; | |
| 132 propertyName.range.endLine = lineNumber; | |
| 133 propertyName.range.endColumn = column; | |
| 134 propertyValue = new WebInspector.SASSSupport.TextNode("", WebIns pector.TextRange.createFromLocation(lineNumber, newColumn)); | |
| 135 } else if (tokenType["css-property"]) { | |
| 136 propertyName.text += tokenValue; | |
| 137 } | |
| 138 break; | |
| 139 case States.PropertyValue: | |
| 140 if ((tokenValue === "}" || tokenValue === ";") && tokenType === Unde fTokenType) { | |
| 141 propertyValue.range.endLine = lineNumber; | |
| 142 propertyValue.range.endColumn = column; | |
| 143 var property = new WebInspector.SASSSupport.Property(propertyNam e, propertyValue, propertyName.range.clone(), false); | |
| 144 property.range.endLine = lineNumber; | |
| 145 property.range.endColumn = newColumn; | |
| 146 properties.push(property); | |
| 147 state = States.Initial; | |
| 148 } else { | |
| 149 propertyValue.text += tokenValue; | |
| 150 } | |
| 151 break; | |
| 152 case States.MixinName: | |
| 153 if (tokenValue === "(" && tokenType === UndefTokenType) { | |
| 154 state = States.MixinValue; | |
| 155 mixinName.range.endLine = lineNumber; | |
| 156 mixinName.range.endColumn = column; | |
| 157 mixinValue = new WebInspector.SASSSupport.TextNode("", WebInspec tor.TextRange.createFromLocation(lineNumber, newColumn)); | |
| 158 } else if (tokenValue === ";" && tokenType === UndefTokenType) { | |
| 159 state = States.Initial; | |
| 160 mixinValue = null; | |
| 161 } else { | |
| 162 mixinName.text += tokenValue; | |
| 163 } | |
| 164 break; | |
| 165 case States.MixinValue: | |
| 166 if (tokenValue === ")" && tokenType === UndefTokenType) { | |
| 167 mixinValue.range.endLine = lineNumber; | |
| 168 mixinValue.range.endColumn = column; | |
| 169 var mixin = new WebInspector.SASSSupport.Property(mixinName, /** @type {!WebInspector.SASSSupport.TextNode} */(mixinValue), mixinName.range.clon e(), false); | |
| 170 mixin.range.endLine = lineNumber; | |
| 171 mixin.range.endColumn = newColumn; | |
| 172 mixins.push(mixin); | |
| 173 state = States.Initial; | |
| 174 } else { | |
| 175 mixinValue.text += tokenValue; | |
| 176 } | |
| 177 break; | |
| 178 case States.Media: | |
| 179 if (tokenValue === "{" && tokenType === UndefTokenType) | |
| 180 state = States.Initial; | |
| 181 break; | |
| 182 default: | |
| 183 console.assert(false, "Unknown SASS parser state."); | |
| 184 } | |
| 185 } | |
| 186 var tokenizer = tokenizerFactory.createTokenizer("text/x-scss"); | |
| 187 var lineNumber; | |
| 188 for (lineNumber = 0; lineNumber < lines.length; ++lineNumber) { | |
| 189 var line = lines[lineNumber]; | |
| 190 tokenizer(line, processToken); | |
| 191 processToken("\n", null, line.length, line.length + 1); | |
| 192 } | |
| 193 return { | |
| 194 variables: variables, | |
| 195 properties: properties, | |
| 196 mixins: mixins | |
| 197 }; | |
| 198 } | |
| 199 | |
| 200 /** | |
| 201 * @constructor | |
| 202 */ | |
| 203 WebInspector.SASSSupport.Node = function() | |
| 204 { | |
| 205 this.parent = null; | |
|
pfeldman
2015/12/09 03:50:59
Why is the AST tree not reflective? I.e. why can't
lushnikov
2015/12/09 07:04:57
Because it's structure is simple enough to avoid r
| |
| 206 } | |
| 207 | |
| 208 WebInspector.SASSSupport.Node.prototype = { | |
| 209 /** | |
| 210 * @return {!WebInspector.SASSSupport.AST} | |
| 211 */ | |
| 212 root: function() | |
| 213 { | |
| 214 if (this._root) | |
| 215 return this._root; | |
| 216 this._root = this; | |
| 217 while (this._root.parent) | |
| 218 this._root = this._root.parent; | |
| 219 return /** @type {!WebInspector.SASSSupport.AST} */(this._root); | |
| 220 } | |
| 221 } | |
| 222 | |
| 223 /** | |
| 224 * @constructor | |
| 225 * @extends {WebInspector.SASSSupport.Node} | |
| 226 * @param {string} text | |
| 227 * @param {!WebInspector.TextRange} range | |
| 228 */ | |
| 229 WebInspector.SASSSupport.TextNode = function(text, range) | |
| 230 { | |
| 231 WebInspector.SASSSupport.Node.call(this); | |
| 232 this.text = text; | |
| 233 this.range = range; | |
| 234 } | |
| 235 | |
| 236 WebInspector.SASSSupport.TextNode.prototype = { | |
| 237 /** | |
| 238 * @return {!WebInspector.SASSSupport.TextNode} | |
| 239 */ | |
| 240 clone: function() | |
| 241 { | |
| 242 return new WebInspector.SASSSupport.TextNode(this.text, this.range.clone ()); | |
| 243 }, | |
| 244 | |
| 245 __proto__: WebInspector.SASSSupport.Node.prototype | |
| 246 } | |
| 247 | |
| 248 /** | |
| 249 * @constructor | |
| 250 * @extends {WebInspector.SASSSupport.Node} | |
| 251 * @param {!WebInspector.SASSSupport.TextNode} name | |
| 252 * @param {!WebInspector.SASSSupport.TextNode} value | |
| 253 * @param {!WebInspector.TextRange} range | |
| 254 * @param {boolean} disabled | |
| 255 */ | |
| 256 WebInspector.SASSSupport.Property = function(name, value, range, disabled) | |
| 257 { | |
| 258 WebInspector.SASSSupport.Node.call(this); | |
| 259 this.name = name; | |
| 260 this.value = value; | |
| 261 this.range = range; | |
| 262 this.name.parent = this; | |
| 263 this.value.parent = this; | |
| 264 this.disabled = disabled; | |
| 265 } | |
| 266 | |
| 267 WebInspector.SASSSupport.Property.prototype = { | |
| 268 /** | |
| 269 * @return {!WebInspector.SASSSupport.Property} | |
| 270 */ | |
| 271 clone: function() | |
| 272 { | |
| 273 return new WebInspector.SASSSupport.Property(this.name.clone(), this.val ue.clone(), this.range.clone(), this.disabled); | |
| 274 }, | |
| 275 | |
| 276 __proto__: WebInspector.SASSSupport.Node.prototype | |
| 277 } | |
| 278 | |
| 279 /** | |
| 280 * @constructor | |
| 281 * @extends {WebInspector.SASSSupport.Node} | |
| 282 * @param {string} selector | |
| 283 * @param {!Array<!WebInspector.SASSSupport.Property>} properties | |
| 284 */ | |
| 285 WebInspector.SASSSupport.Rule = function(selector, properties) | |
| 286 { | |
| 287 WebInspector.SASSSupport.Node.call(this); | |
| 288 this.selector = selector; | |
| 289 this.properties = properties; | |
| 290 for (var i = 0; i < this.properties.length; ++i) | |
| 291 this.properties[i].parent = this; | |
| 292 } | |
| 293 | |
| 294 WebInspector.SASSSupport.Rule.prototype = { | |
| 295 /** | |
| 296 * @return {!WebInspector.SASSSupport.Rule} | |
| 297 */ | |
| 298 clone: function() | |
| 299 { | |
| 300 var properties = []; | |
| 301 for (var i = 0; i < this.properties.length; ++i) | |
| 302 properties.push(this.properties[i].clone()); | |
| 303 return new WebInspector.SASSSupport.Rule(this.selector, properties); | |
| 304 }, | |
| 305 | |
| 306 __proto__: WebInspector.SASSSupport.Node.prototype | |
| 307 } | |
| 308 | |
| 309 /** | |
| 310 * @constructor | |
| 311 * @extends {WebInspector.SASSSupport.Node} | |
| 312 * @param {string} url | |
| 313 * @param {string} text | |
| 314 * @param {!Array<!WebInspector.SASSSupport.Rule>} rules | |
| 315 */ | |
| 316 WebInspector.SASSSupport.AST = function(url, text, rules) | |
| 317 { | |
| 318 WebInspector.SASSSupport.Node.call(this); | |
| 319 this.url = url; | |
| 320 this.rules = rules; | |
| 321 for (var i = 0; i < rules.length; ++i) | |
| 322 rules[i].parent = this; | |
| 323 this.text = text; | |
| 324 } | |
| 325 | |
| 326 WebInspector.SASSSupport.AST.prototype = { | |
| 327 /** | |
| 328 * @return {!WebInspector.SASSSupport.AST} | |
| 329 */ | |
| 330 clone: function() | |
| 331 { | |
| 332 var rules = []; | |
| 333 for (var i = 0; i < this.rules.length; ++i) | |
| 334 rules.push(this.rules[i].clone()); | |
| 335 return new WebInspector.SASSSupport.AST(this.url, this.text, rules); | |
| 336 }, | |
| 337 | |
| 338 __proto__: WebInspector.SASSSupport.Node.prototype | |
| 339 } | |
| 340 | |
| OLD | NEW |