Chromium Code Reviews| Index: third_party/WebKit/Source/devtools/front_end/sass/SASSSupport.js |
| diff --git a/third_party/WebKit/Source/devtools/front_end/sass/SASSSupport.js b/third_party/WebKit/Source/devtools/front_end/sass/SASSSupport.js |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..0b2e22f87d74b81f5a67744ab095bfe0110e4965 |
| --- /dev/null |
| +++ b/third_party/WebKit/Source/devtools/front_end/sass/SASSSupport.js |
| @@ -0,0 +1,340 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +WebInspector.SASSSupport = {} |
| + |
| +/** |
| + * @param {string} url |
| + * @param {string} text |
| + * @param {!WebInspector.TokenizerFactory} tokenizerFactory |
| + * @return {!WebInspector.SASSSupport.AST} |
| + */ |
| +WebInspector.SASSSupport.parseSCSS = function(url, text, tokenizerFactory) |
| +{ |
| + var result = WebInspector.SASSSupport._innerParseSCSS(text, tokenizerFactory); |
| + |
| + var rules = [ |
| + new WebInspector.SASSSupport.Rule("variables", result.variables), |
| + new WebInspector.SASSSupport.Rule("properties", result.properties), |
| + new WebInspector.SASSSupport.Rule("mixins", result.mixins) |
| + ]; |
| + |
| + return new WebInspector.SASSSupport.AST(url, text, rules); |
| +} |
| + |
| +/** @enum {string} */ |
| +WebInspector.SASSSupport.SCSSParserStates = { |
| + Initial: "Initial", |
| + PropertyName: "PropertyName", |
| + PropertyValue: "PropertyValue", |
| + VariableName: "VariableName", |
| + VariableValue: "VariableValue", |
| + MixinName: "MixinName", |
| + MixinValue: "MixinValue", |
| + Media: "Media", |
| +} |
| + |
| +/** |
| + * @param {string} text |
| + * @param {!WebInspector.TokenizerFactory} tokenizerFactory |
| + * @return {!{variables: !Array<!WebInspector.SASSSupport.Property>, properties: !Array<!WebInspector.SASSSupport.Property>, mixins: !Array<!WebInspector.SASSSupport.Property>}} |
| + */ |
| +WebInspector.SASSSupport._innerParseSCSS = function(text, tokenizerFactory) |
| +{ |
| + var lines = text.split("\n"); |
| + var properties = []; |
| + var variables = []; |
| + var mixins = []; |
| + |
| + var States = WebInspector.SASSSupport.SCSSParserStates; |
| + var state = States.Initial; |
| + var propertyName, propertyValue; |
| + var variableName, variableValue; |
| + var mixinName, mixinValue; |
| + var UndefTokenType = {}; |
| + |
| + /** |
| + * @param {string} tokenValue |
| + * @param {?string} tokenTypes |
| + * @param {number} column |
| + * @param {number} newColumn |
| + */ |
| + function processToken(tokenValue, tokenTypes, column, newColumn) |
| + { |
| + var tokenType = tokenTypes ? tokenTypes.split(" ").keySet() : UndefTokenType; |
| + switch (state) { |
| + case States.Initial: |
| + if (tokenType["css-variable-2"]) { |
| + variableName = new WebInspector.SASSSupport.TextNode(tokenValue, new WebInspector.TextRange(lineNumber, column, lineNumber, newColumn)); |
| + state = States.VariableName; |
| + } else if (tokenType["css-property"] || tokenType["css-meta"]) { |
| + propertyName = new WebInspector.SASSSupport.TextNode(tokenValue, new WebInspector.TextRange(lineNumber, column, lineNumber, newColumn)); |
| + state = States.PropertyName; |
| + } else if (tokenType["css-def"] && tokenValue === "@include") { |
| + mixinName = new WebInspector.SASSSupport.TextNode(tokenValue, new WebInspector.TextRange(lineNumber, column, lineNumber, newColumn)); |
| + state = States.MixinName; |
| + } else if (tokenType["css-comment"]) { |
| + // The |processToken| is called per-line, so no token spans more then one line. |
|
pfeldman
2015/12/09 03:50:59
huh?
|
| + // Support only a one-line comments. |
| + if (tokenValue.substring(0, 2) !== "/*" || tokenValue.substring(tokenValue.length - 2) !== "*/") |
| + break; |
| + var uncommentedText = tokenValue.substring(2, tokenValue.length - 2); |
| + var fakeRule = "a{\n" + uncommentedText + "}"; |
| + var result = WebInspector.SASSSupport._innerParseSCSS(fakeRule, tokenizerFactory); |
| + if (result.properties.length === 1 && result.variables.length === 0 && result.mixins.length === 0) { |
| + var disabledProperty = result.properties[0]; |
| + // We should offset property to current coordinates. |
| + var offset = column + 2; |
| + disabledProperty.disabled = true; |
| + disabledProperty.range = WebInspector.TextRange.createFromLocation(lineNumber, column); |
| + disabledProperty.range.endColumn = newColumn; |
| + disabledProperty.name.range.startLine = lineNumber; |
| + disabledProperty.name.range.startColumn += offset; |
| + disabledProperty.name.range.endLine = lineNumber; |
| + disabledProperty.name.range.endColumn += offset; |
| + disabledProperty.value.range.startLine = lineNumber; |
| + disabledProperty.value.range.startColumn += offset; |
| + disabledProperty.value.range.endLine = lineNumber; |
| + disabledProperty.value.range.endColumn += offset; |
| + properties.push(disabledProperty); |
| + } |
| + } else if (tokenType["css-def"] && tokenValue === "@media") { |
| + state = States.Media; |
| + } |
| + break; |
| + case States.VariableName: |
| + if (tokenValue === ")" && tokenType === UndefTokenType) { |
| + state = States.Initial; |
| + } else if (tokenValue === ":" && tokenType === UndefTokenType) { |
| + state = States.VariableValue; |
| + variableValue = new WebInspector.SASSSupport.TextNode("", WebInspector.TextRange.createFromLocation(lineNumber, newColumn)); |
| + } else if (tokenType !== UndefTokenType) { |
| + state = States.Initial; |
| + } |
| + break; |
| + case States.VariableValue: |
| + if (tokenValue === ";" && tokenType === UndefTokenType) { |
| + variableValue.range.endLine = lineNumber; |
| + variableValue.range.endColumn = column; |
| + var variable = new WebInspector.SASSSupport.Property(variableName, variableValue, variableName.range.clone(), false); |
| + variable.range.endLine = lineNumber; |
| + variable.range.endColumn = newColumn; |
| + variables.push(variable); |
| + state = States.Initial; |
| + } else { |
| + variableValue.text += tokenValue; |
| + } |
| + break; |
| + case States.PropertyName: |
| + if (tokenValue === ":" && tokenType === UndefTokenType) { |
| + state = States.PropertyValue; |
| + propertyName.range.endLine = lineNumber; |
| + propertyName.range.endColumn = column; |
| + propertyValue = new WebInspector.SASSSupport.TextNode("", WebInspector.TextRange.createFromLocation(lineNumber, newColumn)); |
| + } else if (tokenType["css-property"]) { |
| + propertyName.text += tokenValue; |
| + } |
| + break; |
| + case States.PropertyValue: |
| + if ((tokenValue === "}" || tokenValue === ";") && tokenType === UndefTokenType) { |
| + propertyValue.range.endLine = lineNumber; |
| + propertyValue.range.endColumn = column; |
| + var property = new WebInspector.SASSSupport.Property(propertyName, propertyValue, propertyName.range.clone(), false); |
| + property.range.endLine = lineNumber; |
| + property.range.endColumn = newColumn; |
| + properties.push(property); |
| + state = States.Initial; |
| + } else { |
| + propertyValue.text += tokenValue; |
| + } |
| + break; |
| + case States.MixinName: |
| + if (tokenValue === "(" && tokenType === UndefTokenType) { |
| + state = States.MixinValue; |
| + mixinName.range.endLine = lineNumber; |
| + mixinName.range.endColumn = column; |
| + mixinValue = new WebInspector.SASSSupport.TextNode("", WebInspector.TextRange.createFromLocation(lineNumber, newColumn)); |
| + } else if (tokenValue === ";" && tokenType === UndefTokenType) { |
| + state = States.Initial; |
| + mixinValue = null; |
| + } else { |
| + mixinName.text += tokenValue; |
| + } |
| + break; |
| + case States.MixinValue: |
| + if (tokenValue === ")" && tokenType === UndefTokenType) { |
| + mixinValue.range.endLine = lineNumber; |
| + mixinValue.range.endColumn = column; |
| + var mixin = new WebInspector.SASSSupport.Property(mixinName, /** @type {!WebInspector.SASSSupport.TextNode} */(mixinValue), mixinName.range.clone(), false); |
| + mixin.range.endLine = lineNumber; |
| + mixin.range.endColumn = newColumn; |
| + mixins.push(mixin); |
| + state = States.Initial; |
| + } else { |
| + mixinValue.text += tokenValue; |
| + } |
| + break; |
| + case States.Media: |
| + if (tokenValue === "{" && tokenType === UndefTokenType) |
| + state = States.Initial; |
| + break; |
| + default: |
| + console.assert(false, "Unknown SASS parser state."); |
| + } |
| + } |
| + var tokenizer = tokenizerFactory.createTokenizer("text/x-scss"); |
| + var lineNumber; |
| + for (lineNumber = 0; lineNumber < lines.length; ++lineNumber) { |
| + var line = lines[lineNumber]; |
| + tokenizer(line, processToken); |
| + processToken("\n", null, line.length, line.length + 1); |
| + } |
| + return { |
| + variables: variables, |
| + properties: properties, |
| + mixins: mixins |
| + }; |
| +} |
| + |
| +/** |
| + * @constructor |
| + */ |
| +WebInspector.SASSSupport.Node = function() |
| +{ |
| + 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
|
| +} |
| + |
| +WebInspector.SASSSupport.Node.prototype = { |
| + /** |
| + * @return {!WebInspector.SASSSupport.AST} |
| + */ |
| + root: function() |
| + { |
| + if (this._root) |
| + return this._root; |
| + this._root = this; |
| + while (this._root.parent) |
| + this._root = this._root.parent; |
| + return /** @type {!WebInspector.SASSSupport.AST} */(this._root); |
| + } |
| +} |
| + |
| +/** |
| + * @constructor |
| + * @extends {WebInspector.SASSSupport.Node} |
| + * @param {string} text |
| + * @param {!WebInspector.TextRange} range |
| + */ |
| +WebInspector.SASSSupport.TextNode = function(text, range) |
| +{ |
| + WebInspector.SASSSupport.Node.call(this); |
| + this.text = text; |
| + this.range = range; |
| +} |
| + |
| +WebInspector.SASSSupport.TextNode.prototype = { |
| + /** |
| + * @return {!WebInspector.SASSSupport.TextNode} |
| + */ |
| + clone: function() |
| + { |
| + return new WebInspector.SASSSupport.TextNode(this.text, this.range.clone()); |
| + }, |
| + |
| + __proto__: WebInspector.SASSSupport.Node.prototype |
| +} |
| + |
| +/** |
| + * @constructor |
| + * @extends {WebInspector.SASSSupport.Node} |
| + * @param {!WebInspector.SASSSupport.TextNode} name |
| + * @param {!WebInspector.SASSSupport.TextNode} value |
| + * @param {!WebInspector.TextRange} range |
| + * @param {boolean} disabled |
| + */ |
| +WebInspector.SASSSupport.Property = function(name, value, range, disabled) |
| +{ |
| + WebInspector.SASSSupport.Node.call(this); |
| + this.name = name; |
| + this.value = value; |
| + this.range = range; |
| + this.name.parent = this; |
| + this.value.parent = this; |
| + this.disabled = disabled; |
| +} |
| + |
| +WebInspector.SASSSupport.Property.prototype = { |
| + /** |
| + * @return {!WebInspector.SASSSupport.Property} |
| + */ |
| + clone: function() |
| + { |
| + return new WebInspector.SASSSupport.Property(this.name.clone(), this.value.clone(), this.range.clone(), this.disabled); |
| + }, |
| + |
| + __proto__: WebInspector.SASSSupport.Node.prototype |
| +} |
| + |
| +/** |
| + * @constructor |
| + * @extends {WebInspector.SASSSupport.Node} |
| + * @param {string} selector |
| + * @param {!Array<!WebInspector.SASSSupport.Property>} properties |
| + */ |
| +WebInspector.SASSSupport.Rule = function(selector, properties) |
| +{ |
| + WebInspector.SASSSupport.Node.call(this); |
| + this.selector = selector; |
| + this.properties = properties; |
| + for (var i = 0; i < this.properties.length; ++i) |
| + this.properties[i].parent = this; |
| +} |
| + |
| +WebInspector.SASSSupport.Rule.prototype = { |
| + /** |
| + * @return {!WebInspector.SASSSupport.Rule} |
| + */ |
| + clone: function() |
| + { |
| + var properties = []; |
| + for (var i = 0; i < this.properties.length; ++i) |
| + properties.push(this.properties[i].clone()); |
| + return new WebInspector.SASSSupport.Rule(this.selector, properties); |
| + }, |
| + |
| + __proto__: WebInspector.SASSSupport.Node.prototype |
| +} |
| + |
| +/** |
| + * @constructor |
| + * @extends {WebInspector.SASSSupport.Node} |
| + * @param {string} url |
| + * @param {string} text |
| + * @param {!Array<!WebInspector.SASSSupport.Rule>} rules |
| + */ |
| +WebInspector.SASSSupport.AST = function(url, text, rules) |
| +{ |
| + WebInspector.SASSSupport.Node.call(this); |
| + this.url = url; |
| + this.rules = rules; |
| + for (var i = 0; i < rules.length; ++i) |
| + rules[i].parent = this; |
| + this.text = text; |
| +} |
| + |
| +WebInspector.SASSSupport.AST.prototype = { |
| + /** |
| + * @return {!WebInspector.SASSSupport.AST} |
| + */ |
| + clone: function() |
| + { |
| + var rules = []; |
| + for (var i = 0; i < this.rules.length; ++i) |
| + rules.push(this.rules[i].clone()); |
| + return new WebInspector.SASSSupport.AST(this.url, this.text, rules); |
| + }, |
| + |
| + __proto__: WebInspector.SASSSupport.Node.prototype |
| +} |
| + |