Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / script_formatter_worker / CSSFormatter.js
1 /*
2  * Copyright (C) 2013 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 /**
32  * @constructor
33  * @param {string} content
34  * @param {!FormatterWorker.CSSFormattedContentBuilder} builder
35  */
36 FormatterWorker.CSSFormatter = function(content, builder)
37 {
38     this._content = content;
39     this._builder = builder;
40     this._lastLine = -1;
41     this._state = {};
42 }
43
44 FormatterWorker.CSSFormatter.prototype = {
45     format: function()
46     {
47         this._lineEndings = this._lineEndings(this._content);
48         var tokenize = FormatterWorker.createTokenizer("text/css");
49         var lines = this._content.split("\n");
50
51         for (var i = 0; i < lines.length; ++i) {
52             var line = lines[i];
53             tokenize(line, this._tokenCallback.bind(this, i));
54         }
55         this._builder.flushNewLines(true);
56     },
57
58     /**
59      * @param {string} text
60      */
61     _lineEndings: function(text)
62     {
63         var lineEndings = [];
64         var i = text.indexOf("\n");
65         while (i !== -1) {
66             lineEndings.push(i);
67             i = text.indexOf("\n", i + 1);
68         }
69         lineEndings.push(text.length);
70         return lineEndings;
71     },
72
73     /**
74      * @param {number} startLine
75      * @param {string} token
76      * @param {?string} type
77      * @param {number} startColumn
78      */
79     _tokenCallback: function(startLine, token, type, startColumn)
80     {
81         if (startLine !== this._lastLine)
82             this._state.eatWhitespace = true;
83         if (/^property/.test(type) && !this._state.inPropertyValue)
84             this._state.seenProperty = true;
85         this._lastLine = startLine;
86         var isWhitespace = /^\s+$/.test(token);
87         if (isWhitespace) {
88             if (!this._state.eatWhitespace)
89                 this._builder.addSpace();
90             return;
91         }
92         this._state.eatWhitespace = false;
93         if (token === "\n")
94             return;
95
96         if (token !== "}") {
97             if (this._state.afterClosingBrace)
98                 this._builder.addNewLine();
99             this._state.afterClosingBrace = false;
100         }
101         var startPosition = (startLine ? this._lineEndings[startLine - 1] : 0) + startColumn;
102         if (token === "}") {
103             if (this._state.inPropertyValue)
104                 this._builder.addNewLine();
105             this._builder.decreaseNestingLevel();
106             this._state.afterClosingBrace = true;
107             this._state.inPropertyValue = false;
108         } else if (token === ":" && !this._state.inPropertyValue && this._state.seenProperty) {
109             this._builder.addToken(token, startPosition, startLine, startColumn);
110             this._builder.addSpace();
111             this._state.eatWhitespace = true;
112             this._state.inPropertyValue = true;
113             this._state.seenProperty = false;
114             return;
115         } else if (token === "{") {
116             this._builder.addSpace();
117             this._builder.addToken(token, startPosition, startLine, startColumn);
118             this._builder.addNewLine();
119             this._builder.increaseNestingLevel();
120             return;
121         }
122
123         this._builder.addToken(token, startPosition, startLine, startColumn);
124
125         if (type === "comment" && !this._state.inPropertyValue && !this._state.seenProperty)
126             this._builder.addNewLine();
127         if (token === ";" && this._state.inPropertyValue) {
128             this._state.inPropertyValue = false;
129             this._builder.addNewLine();
130         } else if (token === "}") {
131             this._builder.addNewLine();
132         }
133     }
134 }
135
136 /**
137  * @constructor
138  * @param {string} content
139  * @param {!{original: !Array.<number>, formatted: !Array.<number>}} mapping
140  * @param {number} originalOffset
141  * @param {number} formattedOffset
142  * @param {string} indentString
143  */
144 FormatterWorker.CSSFormattedContentBuilder = function(content, mapping, originalOffset, formattedOffset, indentString)
145 {
146     this._originalContent = content;
147     this._originalOffset = originalOffset;
148     this._lastOriginalPosition = 0;
149
150     this._formattedContent = [];
151     this._formattedContentLength = 0;
152     this._formattedOffset = formattedOffset;
153     this._lastFormattedPosition = 0;
154
155     this._mapping = mapping;
156
157     this._lineNumber = 0;
158     this._nestingLevel = 0;
159     this._needNewLines = 0;
160     this._atLineStart = true;
161     this._indentString = indentString;
162     this._cachedIndents = {};
163 }
164
165 FormatterWorker.CSSFormattedContentBuilder.prototype = {
166     /**
167      * @param {string} token
168      * @param {number} startPosition
169      * @param {number} startLine
170      * @param {number} startColumn
171      */
172     addToken: function(token, startPosition, startLine, startColumn)
173     {
174         if ((this._isWhitespaceRun || this._atLineStart) && /^\s+$/.test(token))
175             return;
176
177         if (this._isWhitespaceRun && this._lineNumber === startLine && !this._needNewLines)
178             this._addText(" ");
179
180         this._isWhitespaceRun = false;
181         this._atLineStart = false;
182
183         while (this._lineNumber < startLine) {
184             this._addText("\n");
185             this._addIndent();
186             this._needNewLines = 0;
187             this._lineNumber += 1;
188             this._atLineStart = true;
189         }
190
191         if (this._needNewLines) {
192             this.flushNewLines();
193             this._addIndent();
194             this._atLineStart = true;
195         }
196
197         this._addMappingIfNeeded(startPosition);
198         this._addText(token);
199         this._lineNumber = startLine;
200     },
201
202     addSpace: function()
203     {
204         if (this._isWhitespaceRun)
205             return;
206         this._isWhitespaceRun = true;
207     },
208
209     addNewLine: function()
210     {
211         ++this._needNewLines;
212     },
213
214     /**
215      * @param {boolean=} atLeastOne
216      */
217     flushNewLines: function(atLeastOne)
218     {
219         var newLineCount = atLeastOne && !this._needNewLines ? 1 : this._needNewLines;
220         if (newLineCount)
221             this._isWhitespaceRun = false;
222         for (var i = 0; i < newLineCount; ++i)
223             this._addText("\n");
224         this._needNewLines = 0;
225     },
226
227     increaseNestingLevel: function()
228     {
229         this._nestingLevel += 1;
230     },
231
232     /**
233      * @param {boolean=} addNewline
234      */
235     decreaseNestingLevel: function(addNewline)
236     {
237         if (this._nestingLevel)
238             this._nestingLevel -= 1;
239         if (addNewline)
240             this.addNewLine();
241     },
242
243     /**
244      * @return {string}
245      */
246     content: function()
247     {
248         return this._formattedContent.join("");
249     },
250
251     _addIndent: function()
252     {
253         if (this._cachedIndents[this._nestingLevel]) {
254             this._addText(this._cachedIndents[this._nestingLevel]);
255             return;
256         }
257
258         var fullIndent = "";
259         for (var i = 0; i < this._nestingLevel; ++i)
260             fullIndent += this._indentString;
261         this._addText(fullIndent);
262
263         // Cache a maximum of 20 nesting level indents.
264         if (this._nestingLevel <= 20)
265             this._cachedIndents[this._nestingLevel] = fullIndent;
266     },
267
268     /**
269      * @param {string} text
270      */
271     _addText: function(text)
272     {
273         if (!text)
274             return;
275         this._formattedContent.push(text);
276         this._formattedContentLength += text.length;
277     },
278
279     /**
280      * @param {number} originalPosition
281      */
282     _addMappingIfNeeded: function(originalPosition)
283     {
284         if (originalPosition - this._lastOriginalPosition === this._formattedContentLength - this._lastFormattedPosition)
285             return;
286         this._mapping.original.push(this._originalOffset + originalPosition);
287         this._lastOriginalPosition = originalPosition;
288         this._mapping.formatted.push(this._formattedOffset + this._formattedContentLength);
289         this._lastFormattedPosition = this._formattedContentLength;
290     }
291 }