Index: Source/devtools/front_end/bindings/SASSStructureMapping.js |
diff --git a/Source/devtools/front_end/bindings/SASSStructureMapping.js b/Source/devtools/front_end/bindings/SASSStructureMapping.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..593a9667492e0ad39147dd5cd4b9d7ab60b8fa60 |
--- /dev/null |
+++ b/Source/devtools/front_end/bindings/SASSStructureMapping.js |
@@ -0,0 +1,301 @@ |
+// 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. |
+ |
+/** |
+ * @constructor |
+ * @param {!Map<string, string>} sassSources |
+ */ |
+WebInspector.SASSStructureMapping = function(cssStruct, sassSources) |
+{ |
+ this._sources = sassSources; |
+ this._cssStruct = cssStruct; |
+ this._sassStructs = new Map(); |
+} |
+ |
+WebInspector.SASSStructureMapping.prototype = { |
+ cssPropertyValueToSASS: function(cssValueRange, sourceMap) |
+ { |
+ var entry = sourceMap.findEntry(cssValueRange.endLine, cssValueRange.endColumn); |
+ var url = entry.sourceURL; |
+ var struct = this._structForURL(url); |
+ if (!struct) { |
+ console.error("Failed to get structure of " + url); |
+ return null; |
+ } |
+ var sassValue = this._sassValue(struct, entry); |
+ if (!sassValue) |
+ return null; |
+ return { |
+ url: url, |
+ range: sassValue.range |
+ }; |
+ }, |
+ |
+ cssPropertyToSASS: function(cssPropertyRange, sourceMap) |
+ { |
+ var entry = sourceMap.findEntry(cssPropertyRange.startLine, cssPropertyRange.startColumn); |
+ var struct = this._structForURL(entry.sourceURL); |
+ if (!struct) { |
+ console.error("Failed to get structure of " + entry.sourceURL); |
+ return null; |
+ } |
+ var sassProperty = this._sassProperty(struct, entry); |
+ if (!sassProperty) |
+ return null; |
+ return { |
+ url: entry.sourceURL, |
+ range: sassProperty.range |
+ }; |
+ }, |
+ |
+ sassPropertyValueToCSS: function(sassURL, sassValueRange, sourceMap) |
+ { |
+ var reversedEntries = sourceMap.findEntriesReversed(sassURL, sassValueRange.endLine, sassValueRange.endColumn - 1); |
+ var result = []; |
+ for (var i = 0; i < reversedEntries.length; ++i) { |
+ var value = this._cssValue(reversedEntries[i]); |
+ console.assert(value, "Failed to find reversed mapped property value."); |
+ result.push(value); |
+ } |
+ return result; |
+ }, |
+ |
+ _sassProperty: function(struct, entry) |
+ { |
+ for (var i = 0; i < struct.properties.length; ++i) { |
+ var property = struct.properties[i]; |
+ if (this._rangeContainsSourceMapping(property.range, entry)) |
+ return property; |
+ } |
+ return null; |
+ }, |
+ |
+ _sassVariable: function(struct, entry) |
+ { |
+ for (var i = 0; i < struct.variables.length; ++i) { |
+ var variable = struct.variables[i]; |
+ if (this._rangeContainsSourceMapping(variable.range, entry)) |
+ return variable; |
+ } |
+ return null; |
+ }, |
+ |
+ _sassValue: function(struct, entry) |
+ { |
+ var sassProperty = this._sassProperty(struct, entry); |
+ if (sassProperty && this._rangeContainsSourceMapping(sassProperty.value.range, entry)) |
+ return sassProperty.value; |
+ |
+ var sassVariable = this._sassVariable(struct, entry); |
+ if (sassVariable && this._rangeContainsSourceMapping(sassVariable.value.range, entry)) |
+ return sassVariable.value; |
+ for (var i = 0; i < struct.mixinValues.length; ++i) { |
+ var mixinValue = struct.mixinValues[i]; |
+ if (this._rangeContainsSourceMapping(mixinValue.range, entry)) |
+ return mixinValue; |
+ } |
+ return null; |
+ }, |
+ |
+ _cssProperty: function(entry) |
+ { |
+ for (var i = 0; i < this._cssStruct.length; ++i) { |
+ var rule = this._cssStruct[i]; |
+ for (var j = 0; j < rule.properties.length; ++j) { |
+ var property = rule.properties[j]; |
+ if (this._rangeContainsCompiledMapping(property.range, entry)) |
+ return property; |
+ } |
+ } |
+ return null; |
+ }, |
+ |
+ _cssValue: function(entry) |
+ { |
+ var cssProperty = this._cssProperty(entry); |
+ if (cssProperty && this._rangeContainsCompiledMapping(cssProperty.valueRange, entry)) |
+ return cssProperty; |
+ return null; |
+ }, |
+ |
+ _rangeContainsSourceMapping: function(range, entry) |
+ { |
+ if (range.startLine === range.endLine) |
+ return range.startLine === entry.sourceLineNumber && range.startColumn <= entry.sourceColumnNumber && entry.sourceColumnNumber <= range.endColumn; |
+ if (range.startLine === entry.sourceLineNumber) |
+ return range.startColumn <= entry.sourceColumnNumber; |
+ if (range.endLine === entry.sourceLineNumber) |
+ return entry.sourceColumnNumber <= range.endColumn; |
+ return range.startLine < entry.sourceLineNumber && entry.sourceLineNumber < range.endLine; |
+ }, |
+ |
+ _rangeContainsCompiledMapping: function(range, entry) |
+ { |
+ if (range.startLine === range.endLine) |
+ return range.startLine === entry.lineNumber && range.startColumn <= entry.columnNumber && entry.columnNumber <= range.endColumn; |
+ if (range.startLine === entry.lineNumber) |
+ return range.startColumn <= entry.columnNumber; |
+ if (range.endLine === entry.lineNumber) |
+ return entry.columnNumber <= range.endColumn; |
+ return range.startLine < entry.lineNumber && entry.lineNumber < range.endLine; |
+ }, |
+ |
+ _structForURL: function(sassURL) |
+ { |
+ if (this._sassStructs.has(sassURL)) |
+ return this._sassStructs.get(sassURL); |
+ var source = this._sources.get(sassURL); |
+ if (!source) |
+ return null; |
+ var result = WebInspector.SASSStructureMapping.parseSASS(source); |
+ this._sassStructs.set(sassURL, result); |
+ return result; |
+ } |
+} |
+ |
+ |
+WebInspector.SASSStructureMapping.SCSSParserStates = { |
+ Initial: "Initial", |
+ PropertyName: "PropertyName", |
+ PropertyValue: "PropertyValue", |
+ VariableName: "VariableName", |
+ VariableValue: "VariableValue", |
+ MixinInclude: "MixinInclude", |
+ MixinValue: "MixinValue" |
+} |
+ |
+WebInspector.SASSStructureMapping.parseSASS = function(text) |
+{ |
+ var lines = text.split("\n"); |
+ var properties = []; |
+ var variables = []; |
+ var mixinValues = []; |
+ |
+ var States = WebInspector.SASSStructureMapping.SCSSParserStates; |
+ var state = States.Initial; |
+ var property; |
+ var variable; |
+ var mixin; |
+ 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"]) { |
+ variable = { |
+ name: { |
+ content: tokenValue, |
+ range: new WebInspector.TextRange(lineNumber, column, lineNumber, newColumn) |
+ }, |
+ value: { |
+ content: "", |
+ }, |
+ range: WebInspector.TextRange.createFromLocation(lineNumber, column) |
+ } |
+ state = States.VariableName; |
+ } else if (tokenType["css-property"] || tokenType["css-meta"]) { |
+ property = { |
+ name: { |
+ content: tokenValue, |
+ range: new WebInspector.TextRange(lineNumber, column, lineNumber, newColumn) |
+ }, |
+ value: { |
+ content: "", |
+ }, |
+ range: WebInspector.TextRange.createFromLocation(lineNumber, column) |
+ } |
+ state = States.PropertyName; |
+ } else if (tokenType["css-def"] && tokenValue === "@include") { |
+ state = States.MixinInclude; |
+ } |
+ break; |
+ case States.VariableName: |
+ if (tokenValue === ")" && tokenType === UndefTokenType) { |
+ state = States.Initial; |
+ } else if (tokenValue === ":" && tokenType === UndefTokenType) { |
+ state = States.VariableValue; |
+ variable.value.range = WebInspector.TextRange.createFromLocation(lineNumber, newColumn); |
+ } else if (tokenType !== UndefTokenType) { |
+ state = States.Initial; |
+ } |
+ break; |
+ case States.VariableValue: |
+ if (tokenValue === ";" && tokenType === UndefTokenType) { |
+ variable.value.range.endLine = lineNumber; |
+ variable.value.range.endColumn = column; |
+ variable.range.endLine = lineNumber; |
+ variable.range.endColumn = newColumn; |
+ variables.push(variable); |
+ state = States.Initial; |
+ } else { |
+ variable.value.content += tokenValue; |
+ } |
+ break; |
+ case States.PropertyName: |
+ if (tokenValue === ":" && tokenType === UndefTokenType) { |
+ state = States.PropertyValue; |
+ property.value.range = WebInspector.TextRange.createFromLocation(lineNumber, newColumn); |
+ } else if (tokenType["property"]) { |
+ property.name.contet += tokenValue; |
+ } |
+ break; |
+ case States.PropertyValue: |
+ if (tokenValue === ";" && tokenType === UndefTokenType) { |
+ property.value.range.endLine = lineNumber; |
+ property.value.range.endColumn = column; |
+ property.range.endLine = lineNumber; |
+ property.range.endColumn = newColumn; |
+ properties.push(property); |
+ state = States.Initial; |
+ } else { |
+ property.value.content += tokenValue; |
+ } |
+ break; |
+ case States.MixinInclude: |
+ if (tokenValue === "(" && tokenType === UndefTokenType) { |
+ state = States.MixinValue; |
+ mixin = { |
+ range: WebInspector.TextRange.createFromLocation(lineNumber, newColumn), |
+ value: "" |
+ }; |
+ } else if (tokenValue === ";" && tokenType === UndefTokenType) { |
+ state = States.Initial; |
+ if (mixin) |
+ mixinValues.push(mixin); |
+ mixin = null; |
+ } |
+ break; |
+ case States.MixinValue: |
+ if (tokenValue === ")" && tokenType === UndefTokenType) { |
+ state = States.MixinInclude; |
+ mixin.range.endLine = lineNumber; |
+ mixin.range.endColumn = column; |
+ } else { |
+ mixin.value += tokenValue; |
+ } |
+ break; |
+ default: |
+ console.assert(false, "Unknown CSS parser state."); |
+ } |
+ } |
+ var tokenizer = new WebInspector.CodeMirrorUtils.TokenizerFactory().createTokenizer("text/x-scss"); |
+ var lineNumber; |
+ for (lineNumber = 0; lineNumber < lines.length; ++lineNumber) { |
+ var line = lines[lineNumber]; |
+ tokenizer(line, processToken); |
+ } |
+ return { |
+ variables: variables, |
+ properties: properties, |
+ mixinValues: mixinValues |
+ }; |
+} |