ruby-clean-css 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. data/.gitmodules +3 -0
  2. data/EXAMPLE.md +25 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +30 -0
  5. data/LICENSE +19 -0
  6. data/README.md +106 -0
  7. data/lib/javascript/clean-css/.gitignore +4 -0
  8. data/lib/javascript/clean-css/.jshintignore +3 -0
  9. data/lib/javascript/clean-css/.jshintrc +14 -0
  10. data/lib/javascript/clean-css/.travis.yml +11 -0
  11. data/lib/javascript/clean-css/History.md +556 -0
  12. data/lib/javascript/clean-css/LICENSE +19 -0
  13. data/lib/javascript/clean-css/README.md +218 -0
  14. data/lib/javascript/clean-css/bin/cleancss +157 -0
  15. data/lib/javascript/clean-css/index.js +1 -0
  16. data/lib/javascript/clean-css/lib/clean.js +426 -0
  17. data/lib/javascript/clean-css/lib/colors/hsl-to-hex.js +64 -0
  18. data/lib/javascript/clean-css/lib/colors/long-to-short-hex.js +12 -0
  19. data/lib/javascript/clean-css/lib/colors/rgb-to-hex.js +14 -0
  20. data/lib/javascript/clean-css/lib/colors/shortener.js +174 -0
  21. data/lib/javascript/clean-css/lib/images/url-rebase.js +32 -0
  22. data/lib/javascript/clean-css/lib/images/url-rewriter.js +60 -0
  23. data/lib/javascript/clean-css/lib/imports/inliner.js +319 -0
  24. data/lib/javascript/clean-css/lib/properties/optimizer.js +276 -0
  25. data/lib/javascript/clean-css/lib/properties/override-compactor.js +116 -0
  26. data/lib/javascript/clean-css/lib/properties/processable.js +859 -0
  27. data/lib/javascript/clean-css/lib/properties/scanner.js +20 -0
  28. data/lib/javascript/clean-css/lib/properties/shorthand-compactor.js +244 -0
  29. data/lib/javascript/clean-css/lib/properties/token.js +184 -0
  30. data/lib/javascript/clean-css/lib/properties/validator.js +140 -0
  31. data/lib/javascript/clean-css/lib/selectors/empty-removal.js +30 -0
  32. data/lib/javascript/clean-css/lib/selectors/optimizer.js +341 -0
  33. data/lib/javascript/clean-css/lib/selectors/tokenizer.js +168 -0
  34. data/lib/javascript/clean-css/lib/text/comments.js +83 -0
  35. data/lib/javascript/clean-css/lib/text/escape-store.js +32 -0
  36. data/lib/javascript/clean-css/lib/text/expressions.js +73 -0
  37. data/lib/javascript/clean-css/lib/text/free.js +26 -0
  38. data/lib/javascript/clean-css/lib/text/name-quotes.js +37 -0
  39. data/lib/javascript/clean-css/lib/text/quote-scanner.js +91 -0
  40. data/lib/javascript/clean-css/lib/text/splitter.js +35 -0
  41. data/lib/javascript/clean-css/lib/text/urls.js +41 -0
  42. data/lib/javascript/clean-css/package.json +50 -0
  43. data/lib/javascript/clean-css/test/batch-test.js +56 -0
  44. data/lib/javascript/clean-css/test/bench.js +11 -0
  45. data/lib/javascript/clean-css/test/binary-test.js +323 -0
  46. data/lib/javascript/clean-css/test/data-bench/_partial.css +1 -0
  47. data/lib/javascript/clean-css/test/data-bench/complex.css +4 -0
  48. data/lib/javascript/clean-css/test/data/129-assets/assets/ui.css +2 -0
  49. data/lib/javascript/clean-css/test/data/129-assets/components/bootstrap/css/bootstrap.css +3 -0
  50. data/lib/javascript/clean-css/test/data/129-assets/components/bootstrap/images/glyphs.gif +0 -0
  51. data/lib/javascript/clean-css/test/data/129-assets/components/jquery-ui/css/style.css +6 -0
  52. data/lib/javascript/clean-css/test/data/129-assets/components/jquery-ui/images/next.gif +0 -0
  53. data/lib/javascript/clean-css/test/data/129-assets/components/jquery-ui/images/prev.gif +0 -0
  54. data/lib/javascript/clean-css/test/data/960-min.css +125 -0
  55. data/lib/javascript/clean-css/test/data/960.css +602 -0
  56. data/lib/javascript/clean-css/test/data/big-min.css +2984 -0
  57. data/lib/javascript/clean-css/test/data/big.css +13794 -0
  58. data/lib/javascript/clean-css/test/data/blueprint-min.css +245 -0
  59. data/lib/javascript/clean-css/test/data/blueprint.css +556 -0
  60. data/lib/javascript/clean-css/test/data/charset-mixed-with-fonts-min.css +3 -0
  61. data/lib/javascript/clean-css/test/data/charset-mixed-with-fonts.css +14 -0
  62. data/lib/javascript/clean-css/test/data/font-awesome-ie7-min.css +392 -0
  63. data/lib/javascript/clean-css/test/data/font-awesome-ie7.css +1203 -0
  64. data/lib/javascript/clean-css/test/data/font-awesome-min.css +319 -0
  65. data/lib/javascript/clean-css/test/data/font-awesome.css +540 -0
  66. data/lib/javascript/clean-css/test/data/imports-min.css +5 -0
  67. data/lib/javascript/clean-css/test/data/imports.css +4 -0
  68. data/lib/javascript/clean-css/test/data/issue-117-snippet-min.css +3 -0
  69. data/lib/javascript/clean-css/test/data/issue-117-snippet.css +19 -0
  70. data/lib/javascript/clean-css/test/data/issue-159-snippet-min.css +7 -0
  71. data/lib/javascript/clean-css/test/data/issue-159-snippet.css +7 -0
  72. data/lib/javascript/clean-css/test/data/issue-192-min.css +1 -0
  73. data/lib/javascript/clean-css/test/data/issue-192.css +8 -0
  74. data/lib/javascript/clean-css/test/data/issue-198-min.css +3 -0
  75. data/lib/javascript/clean-css/test/data/issue-198.css +4 -0
  76. data/lib/javascript/clean-css/test/data/issue-232-min.css +2 -0
  77. data/lib/javascript/clean-css/test/data/issue-232.css +17 -0
  78. data/lib/javascript/clean-css/test/data/issue-241-min.css +2 -0
  79. data/lib/javascript/clean-css/test/data/issue-241.css +2 -0
  80. data/lib/javascript/clean-css/test/data/issue-304-min.css +1 -0
  81. data/lib/javascript/clean-css/test/data/issue-304.css +4 -0
  82. data/lib/javascript/clean-css/test/data/issue-305-min.css +1 -0
  83. data/lib/javascript/clean-css/test/data/issue-305.css +3 -0
  84. data/lib/javascript/clean-css/test/data/issue-308-min.css +1 -0
  85. data/lib/javascript/clean-css/test/data/issue-308.css +5 -0
  86. data/lib/javascript/clean-css/test/data/issue-312-min.css +1 -0
  87. data/lib/javascript/clean-css/test/data/issue-312.css +7 -0
  88. data/lib/javascript/clean-css/test/data/issue-337-min.css +1 -0
  89. data/lib/javascript/clean-css/test/data/issue-337.css +4 -0
  90. data/lib/javascript/clean-css/test/data/line-breaks-in-attributes-min.css +2 -0
  91. data/lib/javascript/clean-css/test/data/line-breaks-in-attributes.css +8 -0
  92. data/lib/javascript/clean-css/test/data/partials-absolute/base.css +3 -0
  93. data/lib/javascript/clean-css/test/data/partials-absolute/base2.css +1 -0
  94. data/lib/javascript/clean-css/test/data/partials-absolute/extra/sub.css +3 -0
  95. data/lib/javascript/clean-css/test/data/partials-relative/base.css +3 -0
  96. data/lib/javascript/clean-css/test/data/partials-relative/extra/included.css +1 -0
  97. data/lib/javascript/clean-css/test/data/partials/comment.css +2 -0
  98. data/lib/javascript/clean-css/test/data/partials/extra/down.gif +0 -0
  99. data/lib/javascript/clean-css/test/data/partials/extra/four.css +3 -0
  100. data/lib/javascript/clean-css/test/data/partials/extra/three.css +1 -0
  101. data/lib/javascript/clean-css/test/data/partials/five.css +1 -0
  102. data/lib/javascript/clean-css/test/data/partials/four.css +1 -0
  103. data/lib/javascript/clean-css/test/data/partials/one.css +1 -0
  104. data/lib/javascript/clean-css/test/data/partials/three.css +1 -0
  105. data/lib/javascript/clean-css/test/data/partials/two.css +5 -0
  106. data/lib/javascript/clean-css/test/data/reset-min.css +12 -0
  107. data/lib/javascript/clean-css/test/data/reset.css +64 -0
  108. data/lib/javascript/clean-css/test/data/sample1-min.css +4 -0
  109. data/lib/javascript/clean-css/test/data/sample1.css +12 -0
  110. data/lib/javascript/clean-css/test/data/unsupported/selectors-ie7.css +20 -0
  111. data/lib/javascript/clean-css/test/data/unsupported/selectors-ie8.css +17 -0
  112. data/lib/javascript/clean-css/test/module-test.js +185 -0
  113. data/lib/javascript/clean-css/test/protocol-imports-test.js +409 -0
  114. data/lib/javascript/clean-css/test/text/splitter-test.js +20 -0
  115. data/lib/javascript/clean-css/test/unit-test.js +2071 -0
  116. data/lib/ruby-clean-css.rb +6 -0
  117. data/lib/ruby-clean-css/compressor.rb +142 -0
  118. data/lib/ruby-clean-css/exports.rb +258 -0
  119. data/lib/ruby-clean-css/railtie.rb +27 -0
  120. data/lib/ruby-clean-css/sprockets.rb +19 -0
  121. data/lib/ruby-clean-css/version.rb +5 -0
  122. data/ruby-clean-css.gemspec +30 -0
  123. data/test/test_compressor.rb +84 -0
  124. metadata +203 -0
@@ -0,0 +1,20 @@
1
+ (function() {
2
+ var OPEN_BRACE = '{';
3
+ var SEMICOLON = ';';
4
+ var COLON = ':';
5
+
6
+ var PropertyScanner = function PropertyScanner(data) {
7
+ this.data = data;
8
+ };
9
+
10
+ PropertyScanner.prototype.nextAt = function(cursor) {
11
+ var lastColon = this.data.lastIndexOf(COLON, cursor);
12
+ var lastOpenBrace = this.data.lastIndexOf(OPEN_BRACE, cursor);
13
+ var lastSemicolon = this.data.lastIndexOf(SEMICOLON, cursor);
14
+ var startAt = Math.max(lastOpenBrace, lastSemicolon);
15
+
16
+ return this.data.substring(startAt + 1, lastColon).trim();
17
+ };
18
+
19
+ module.exports = PropertyScanner;
20
+ })();
@@ -0,0 +1,244 @@
1
+
2
+ // Compacts the tokens by transforming properties into their shorthand notations when possible
3
+
4
+ module.exports = (function () {
5
+ var isHackValue = function (t) { return t.value === '__hack'; };
6
+
7
+ var compactShorthands = function(tokens, isImportant, processable, Token) {
8
+ // Contains the components found so far, grouped by shorthand name
9
+ var componentsSoFar = { };
10
+
11
+ // Initializes a prop in componentsSoFar
12
+ var initSoFar = function (shprop, last, clearAll) {
13
+ var found = {};
14
+ var shorthandPosition;
15
+
16
+ if (!clearAll && componentsSoFar[shprop]) {
17
+ for (var i = 0; i < processable[shprop].components.length; i++) {
18
+ var prop = processable[shprop].components[i];
19
+ found[prop] = [];
20
+
21
+ if (!(componentsSoFar[shprop].found[prop]))
22
+ continue;
23
+
24
+ for (var ii = 0; ii < componentsSoFar[shprop].found[prop].length; ii++) {
25
+ var comp = componentsSoFar[shprop].found[prop][ii];
26
+
27
+ if (comp.isMarkedForDeletion)
28
+ continue;
29
+
30
+ found[prop].push(comp);
31
+
32
+ if (comp.position && (!shorthandPosition || comp.position < shorthandPosition))
33
+ shorthandPosition = comp.position;
34
+ }
35
+ }
36
+ }
37
+ componentsSoFar[shprop] = {
38
+ lastShorthand: last,
39
+ found: found,
40
+ shorthandPosition: shorthandPosition
41
+ };
42
+ };
43
+
44
+ // Adds a component to componentsSoFar
45
+ var addComponentSoFar = function (token, index) {
46
+ var shprop = processable[token.prop].componentOf;
47
+ if (!componentsSoFar[shprop])
48
+ initSoFar(shprop);
49
+ if (!componentsSoFar[shprop].found[token.prop])
50
+ componentsSoFar[shprop].found[token.prop] = [];
51
+
52
+ // Add the newfound component to componentsSoFar
53
+ componentsSoFar[shprop].found[token.prop].push(token);
54
+
55
+ if (!componentsSoFar[shprop].shorthandPosition && index) {
56
+ // If the haven't decided on where the shorthand should go, put it in the place of this component
57
+ componentsSoFar[shprop].shorthandPosition = index;
58
+ }
59
+ };
60
+
61
+ // Tries to compact a prop in componentsSoFar
62
+ var compactSoFar = function (prop) {
63
+ var i;
64
+ var componentsCount = processable[prop].components.length;
65
+
66
+ // Check basics
67
+ if (!componentsSoFar[prop] || !componentsSoFar[prop].found)
68
+ return false;
69
+
70
+ // Find components for the shorthand
71
+ var components = [];
72
+ var realComponents = [];
73
+ for (i = 0 ; i < componentsCount; i++) {
74
+ // Get property name
75
+ var pp = processable[prop].components[i];
76
+
77
+ if (componentsSoFar[prop].found[pp] && componentsSoFar[prop].found[pp].length) {
78
+ // We really found it
79
+ var foundRealComp = componentsSoFar[prop].found[pp][0];
80
+ components.push(foundRealComp);
81
+ if (foundRealComp.isReal !== false) {
82
+ realComponents.push(foundRealComp);
83
+ }
84
+ } else if (componentsSoFar[prop].lastShorthand) {
85
+ // It's defined in the previous shorthand
86
+ var c = componentsSoFar[prop].lastShorthand.components[i].clone(isImportant);
87
+ components.push(c);
88
+ } else {
89
+ // Couldn't find this component at all
90
+ return false;
91
+ }
92
+ }
93
+
94
+ if (realComponents.length === 0) {
95
+ // Couldn't find enough components, sorry
96
+ return false;
97
+ }
98
+
99
+ if (realComponents.length === componentsCount) {
100
+ // When all the components are from real values, only allow shorthanding if their understandability allows it
101
+ // This is the case when every component can override their default values, or when all of them use the same function
102
+
103
+ var canOverrideDefault = true;
104
+ var functionNameMatches = true;
105
+ var functionName;
106
+
107
+ for (var ci = 0; ci < realComponents.length; ci++) {
108
+ var rc = realComponents[ci];
109
+
110
+ if (!processable[rc.prop].canOverride(processable[rc.prop].defaultValue, rc.value)) {
111
+ canOverrideDefault = false;
112
+ }
113
+ var iop = rc.value.indexOf('(');
114
+ if (iop >= 0) {
115
+ var otherFunctionName = rc.value.substring(0, iop);
116
+ if (functionName)
117
+ functionNameMatches = functionNameMatches && otherFunctionName === functionName;
118
+ else
119
+ functionName = otherFunctionName;
120
+ }
121
+ }
122
+
123
+ if (!canOverrideDefault || !functionNameMatches)
124
+ return false;
125
+ }
126
+
127
+ // Compact the components into a shorthand
128
+ var compacted = processable[prop].putTogether(prop, components, isImportant);
129
+ if (!(compacted instanceof Array)) {
130
+ compacted = [compacted];
131
+ }
132
+
133
+ var compactedLength = Token.getDetokenizedLength(compacted);
134
+ var authenticLength = Token.getDetokenizedLength(realComponents);
135
+
136
+ if (realComponents.length === componentsCount || compactedLength < authenticLength || components.some(isHackValue)) {
137
+ compacted[0].isShorthand = true;
138
+ compacted[0].components = processable[prop].breakUp(compacted[0]);
139
+
140
+ // Mark the granular components for deletion
141
+ for (i = 0; i < realComponents.length; i++) {
142
+ realComponents[i].isMarkedForDeletion = true;
143
+ }
144
+
145
+ // Mark the position of the new shorthand
146
+ tokens[componentsSoFar[prop].shorthandPosition].replaceWith = compacted;
147
+
148
+ // Reinitialize the thing for further compacting
149
+ initSoFar(prop, compacted[0]);
150
+ for (i = 1; i < compacted.length; i++) {
151
+ addComponentSoFar(compacted[i]);
152
+ }
153
+
154
+ // Yes, we can keep the new shorthand!
155
+ return true;
156
+ }
157
+
158
+ return false;
159
+ };
160
+
161
+ // Tries to compact all properties currently in componentsSoFar
162
+ var compactAllSoFar = function () {
163
+ for (var i in componentsSoFar) {
164
+ if (componentsSoFar.hasOwnProperty(i)) {
165
+ while (compactSoFar(i)) { }
166
+ }
167
+ }
168
+ };
169
+
170
+ var i, token;
171
+
172
+ // Go through each token and collect components for each shorthand as we go on
173
+ for (i = 0; i < tokens.length; i++) {
174
+ token = tokens[i];
175
+ if (token.isMarkedForDeletion) {
176
+ continue;
177
+ }
178
+ if (!processable[token.prop]) {
179
+ // We don't know what it is, move on
180
+ continue;
181
+ }
182
+ if (processable[token.prop].isShorthand) {
183
+ // Found an instance of a full shorthand
184
+ // NOTE: we should NOT mix together tokens that come before and after the shorthands
185
+
186
+ if (token.isImportant === isImportant || (token.isImportant && !isImportant)) {
187
+ // Try to compact what we've found so far
188
+ while (compactSoFar(token.prop)) { }
189
+ // Reset
190
+ initSoFar(token.prop, token, true);
191
+ }
192
+
193
+ // TODO: when the old optimizer is removed, take care of this corner case:
194
+ // div{background-color:#111;background-image:url(aaa);background:linear-gradient(aaa);background-repeat:no-repeat;background-position:1px 2px;background-attachment:scroll}
195
+ // -> should not be shorthanded / minified at all because the result wouldn't be equivalent to the original in any browser
196
+ } else if (processable[token.prop].componentOf) {
197
+ // Found a component of a shorthand
198
+ if (token.isImportant === isImportant) {
199
+ // Same importantness
200
+ token.position = i;
201
+ addComponentSoFar(token, i);
202
+ } else if (!isImportant && token.isImportant) {
203
+ // Use importants for optimalization opportunities
204
+ // https://github.com/GoalSmashers/clean-css/issues/184
205
+ var importantTrickComp = new Token(token.prop, token.value, isImportant);
206
+ importantTrickComp.isIrrelevant = true;
207
+ importantTrickComp.isReal = false;
208
+ addComponentSoFar(importantTrickComp);
209
+ }
210
+ } else {
211
+ // This is not a shorthand and not a component, don't care about it
212
+ continue;
213
+ }
214
+ }
215
+
216
+ // Perform all possible compactions
217
+ compactAllSoFar();
218
+
219
+ // Process the results - throw away stuff marked for deletion, insert compacted things, etc.
220
+ var result = [];
221
+ for (i = 0; i < tokens.length; i++) {
222
+ token = tokens[i];
223
+
224
+ if (token.replaceWith) {
225
+ for (var ii = 0; ii < token.replaceWith.length; ii++) {
226
+ result.push(token.replaceWith[ii]);
227
+ }
228
+ }
229
+ if (!token.isMarkedForDeletion) {
230
+ result.push(token);
231
+ }
232
+
233
+ token.isMarkedForDeletion = false;
234
+ token.replaceWith = null;
235
+ }
236
+
237
+ return result;
238
+ };
239
+
240
+ return {
241
+ compactShorthands: compactShorthands
242
+ };
243
+
244
+ })();
@@ -0,0 +1,184 @@
1
+
2
+ // Helper for tokenizing the contents of a CSS selector block
3
+
4
+ module.exports = (function() {
5
+ var createTokenPrototype = function (processable) {
6
+ var important = '!important';
7
+
8
+ // Constructor for tokens
9
+ function Token (prop, p2, p3) {
10
+ this.prop = prop;
11
+ if (typeof(p2) === 'string') {
12
+ this.value = p2;
13
+ this.isImportant = p3;
14
+ }
15
+ else {
16
+ this.value = processable[prop].defaultValue;
17
+ this.isImportant = p2;
18
+ }
19
+ }
20
+
21
+ Token.prototype.prop = null;
22
+ Token.prototype.value = null;
23
+ Token.prototype.granularValues = null;
24
+ Token.prototype.components = null;
25
+ Token.prototype.position = null;
26
+ Token.prototype.isImportant = false;
27
+ Token.prototype.isDirty = false;
28
+ Token.prototype.isShorthand = false;
29
+ Token.prototype.isIrrelevant = false;
30
+ Token.prototype.isReal = true;
31
+ Token.prototype.isMarkedForDeletion = false;
32
+
33
+ // Tells if this token is a component of the other one
34
+ Token.prototype.isComponentOf = function (other) {
35
+ if (!processable[this.prop] || !processable[other.prop])
36
+ return false;
37
+ if (!(processable[other.prop].components instanceof Array) || !processable[other.prop].components.length)
38
+ return false;
39
+
40
+ return processable[other.prop].components.indexOf(this.prop) >= 0;
41
+ };
42
+
43
+ // Clones a token
44
+ Token.prototype.clone = function (isImportant) {
45
+ var token = new Token(this.prop, this.value, (typeof(isImportant) !== 'undefined' ? isImportant : this.isImportant));
46
+ return token;
47
+ };
48
+
49
+ // Creates an irrelevant token with the same prop
50
+ Token.prototype.cloneIrrelevant = function (isImportant) {
51
+ var token = Token.makeDefault(this.prop, (typeof(isImportant) !== 'undefined' ? isImportant : this.isImportant));
52
+ token.isIrrelevant = true;
53
+ return token;
54
+ };
55
+
56
+ // Creates an array of property tokens with their default values
57
+ Token.makeDefaults = function (props, important) {
58
+ return props.map(function(prop) {
59
+ return new Token(prop, important);
60
+ });
61
+ };
62
+
63
+ // Parses one CSS property declaration into a token
64
+ Token.tokenizeOne = function (fullProp) {
65
+ // Find first colon
66
+ var colonPos = fullProp.indexOf(':');
67
+
68
+ if (colonPos < 0) {
69
+ // This property doesn't have a colon, it's invalid. Let's keep it intact anyway.
70
+ return new Token('', fullProp);
71
+ }
72
+
73
+ // Parse parts of the property
74
+ var prop = fullProp.substr(0, colonPos).trim();
75
+ var value = fullProp.substr(colonPos + 1).trim();
76
+ var isImportant = false;
77
+ var importantPos = value.indexOf(important);
78
+
79
+ // Check if the property is important
80
+ if (importantPos >= 1 && importantPos === value.length - important.length) {
81
+ value = value.substr(0, importantPos).trim();
82
+ isImportant = true;
83
+ }
84
+
85
+ // Return result
86
+ var result = new Token(prop, value, isImportant);
87
+
88
+ // If this is a shorthand, break up its values
89
+ // NOTE: we need to do this for all shorthands because otherwise we couldn't remove default values from them
90
+ if (processable[prop] && processable[prop].isShorthand) {
91
+ result.isShorthand = true;
92
+ result.components = processable[prop].breakUp(result);
93
+ result.isDirty = true;
94
+ }
95
+
96
+ return result;
97
+ };
98
+
99
+ // Breaks up a string of CSS property declarations into tokens so that they can be handled more easily
100
+ Token.tokenize = function (input) {
101
+ // Split the input by semicolons and parse the parts
102
+ var tokens = input.split(';').map(Token.tokenizeOne);
103
+ return tokens;
104
+ };
105
+
106
+ // Transforms tokens back into CSS properties
107
+ Token.detokenize = function (tokens) {
108
+ // If by mistake the input is not an array, make it an array
109
+ if (!(tokens instanceof Array)) {
110
+ tokens = [tokens];
111
+ }
112
+
113
+ var result = '';
114
+
115
+ // This step takes care of putting together the components of shorthands
116
+ // NOTE: this is necessary to do for every shorthand, otherwise we couldn't remove their default values
117
+ for (var i = 0; i < tokens.length; i++) {
118
+ var t = tokens[i];
119
+ if (t.isShorthand && t.isDirty) {
120
+ var news = processable[t.prop].putTogether(t.prop, t.components, t.isImportant);
121
+ Array.prototype.splice.apply(tokens, [i, 1].concat(news));
122
+ t.isDirty = false;
123
+ i--;
124
+ continue;
125
+ }
126
+
127
+ if (t.prop)
128
+ result += t.prop + ':';
129
+
130
+ if (t.value)
131
+ result += t.value;
132
+
133
+ if (t.isImportant)
134
+ result += important;
135
+
136
+ result += ';';
137
+ }
138
+
139
+ return result.substr(0, result.length - 1);
140
+ };
141
+
142
+ // Gets the final (detokenized) length of the given tokens
143
+ Token.getDetokenizedLength = function (tokens) {
144
+ // If by mistake the input is not an array, make it an array
145
+ if (!(tokens instanceof Array)) {
146
+ tokens = [tokens];
147
+ }
148
+
149
+ var result = 0;
150
+
151
+ // This step takes care of putting together the components of shorthands
152
+ // NOTE: this is necessary to do for every shorthand, otherwise we couldn't remove their default values
153
+ for (var i = 0; i < tokens.length; i++) {
154
+ var t = tokens[i];
155
+ if (t.isShorthand && t.isDirty) {
156
+ var news = processable[t.prop].putTogether(t.prop, t.components, t.isImportant);
157
+ Array.prototype.splice.apply(tokens, [i, 1].concat(news));
158
+ t.isDirty = false;
159
+ i--;
160
+ continue;
161
+ }
162
+
163
+ if (t.prop) {
164
+ result += t.prop.length + 1;
165
+ }
166
+ if (t.value) {
167
+ result += t.value.length;
168
+ }
169
+ if (t.isImportant) {
170
+ result += important.length;
171
+ }
172
+ }
173
+
174
+ return result;
175
+ };
176
+
177
+ return Token;
178
+ };
179
+
180
+ return {
181
+ createTokenPrototype: createTokenPrototype
182
+ };
183
+
184
+ })();
@@ -0,0 +1,140 @@
1
+
2
+ // Validates various CSS property values
3
+
4
+ var Splitter = require('../text/splitter');
5
+
6
+ module.exports = (function () {
7
+ // Regexes used for stuff
8
+ var widthKeywords = ['thin', 'thick', 'medium', 'inherit', 'initial'];
9
+ var cssUnitRegexStr = '(\\-?\\.?\\d+\\.?\\d*(px|%|em|rem|in|cm|mm|ex|pt|pc|vw|vh|vmin|vmax|)|auto|inherit)';
10
+ var cssFunctionNoVendorRegexStr = '[A-Z]+(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]|\\ |\\,|\\#|\\+|\\-|\\%|\\.|\\(|\\))*\\)';
11
+ var cssFunctionVendorRegexStr = '\\-(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]|\\ |\\,|\\#|\\+|\\-|\\%|\\.|\\(|\\))*\\)';
12
+ var cssVariableRegexStr = 'var\\(\\-\\-[^\\)]+\\)';
13
+ var cssFunctionAnyRegexStr = '(' + cssVariableRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')';
14
+ var cssUnitAnyRegexStr = '(none|' + widthKeywords.join('|') + '|' + cssUnitRegexStr + '|' + cssVariableRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')';
15
+
16
+ var cssFunctionNoVendorRegex = new RegExp('^' + cssFunctionNoVendorRegexStr + '$', 'i');
17
+ var cssFunctionVendorRegex = new RegExp('^' + cssFunctionVendorRegexStr + '$', 'i');
18
+ var cssVariableRegex = new RegExp('^' + cssVariableRegexStr + '$', 'i');
19
+ var cssFunctionAnyRegex = new RegExp('^' + cssFunctionAnyRegexStr + '$', 'i');
20
+ var cssUnitRegex = new RegExp('^' + cssUnitRegexStr + '$', 'i');
21
+ var cssUnitAnyRegex = new RegExp('^' + cssUnitAnyRegexStr + '$', 'i');
22
+
23
+ var backgroundRepeatKeywords = ['repeat', 'no-repeat', 'repeat-x', 'repeat-y', 'inherit'];
24
+ var backgroundAttachmentKeywords = ['inherit', 'scroll', 'fixed', 'local'];
25
+ var backgroundPositionKeywords = ['center', 'top', 'bottom', 'left', 'right'];
26
+ var backgroundSizeKeywords = ['contain', 'cover'];
27
+ var listStyleTypeKeywords = ['armenian', 'circle', 'cjk-ideographic', 'decimal', 'decimal-leading-zero', 'disc', 'georgian', 'hebrew', 'hiragana', 'hiragana-iroha', 'inherit', 'katakana', 'katakana-iroha', 'lower-alpha', 'lower-greek', 'lower-latin', 'lower-roman', 'none', 'square', 'upper-alpha', 'upper-latin', 'upper-roman'];
28
+ var listStylePositionKeywords = ['inside', 'outside', 'inherit'];
29
+ var outlineStyleKeywords = ['auto', 'inherit', 'hidden', 'none', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset'];
30
+
31
+ var validator = {
32
+ isValidHexColor: function (s) {
33
+ return (s.length === 4 || s.length === 7) && s[0] === '#';
34
+ },
35
+ isValidRgbaColor: function (s) {
36
+ s = s.split(' ').join('');
37
+ return s.length > 0 && s.indexOf('rgba(') === 0 && s.indexOf(')') === s.length - 1;
38
+ },
39
+ isValidHslaColor: function (s) {
40
+ s = s.split(' ').join('');
41
+ return s.length > 0 && s.indexOf('hsla(') === 0 && s.indexOf(')') === s.length - 1;
42
+ },
43
+ isValidNamedColor: function (s) {
44
+ // We don't really check if it's a valid color value, but allow any letters in it
45
+ return s !== 'auto' && (s === 'transparent' || s === 'inherit' || /^[a-zA-Z]+$/.test(s));
46
+ },
47
+ isValidVariable: function(s) {
48
+ return cssVariableRegex.test(s);
49
+ },
50
+ isValidColor: function (s) {
51
+ return validator.isValidNamedColor(s) || validator.isValidHexColor(s) || validator.isValidRgbaColor(s) || validator.isValidHslaColor(s) || validator.isValidVariable(s);
52
+ },
53
+ isValidUrl: function (s) {
54
+ // NOTE: at this point all URLs are replaced with placeholders by clean-css, so we check for those placeholders
55
+ return s.indexOf('__ESCAPED_URL_CLEAN_CSS') === 0;
56
+ },
57
+ isValidUnit: function (s) {
58
+ return cssUnitAnyRegex.test(s);
59
+ },
60
+ isValidUnitWithoutFunction: function (s) {
61
+ return cssUnitRegex.test(s);
62
+ },
63
+ isValidFunctionWithoutVendorPrefix: function (s) {
64
+ return cssFunctionNoVendorRegex.test(s);
65
+ },
66
+ isValidFunctionWithVendorPrefix: function (s) {
67
+ return cssFunctionVendorRegex.test(s);
68
+ },
69
+ isValidFunction: function (s) {
70
+ return cssFunctionAnyRegex.test(s);
71
+ },
72
+ isValidBackgroundRepeat: function (s) {
73
+ return backgroundRepeatKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
74
+ },
75
+ isValidBackgroundAttachment: function (s) {
76
+ return backgroundAttachmentKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
77
+ },
78
+ isValidBackgroundPositionPart: function (s) {
79
+ if (backgroundPositionKeywords.indexOf(s) >= 0)
80
+ return true;
81
+
82
+ return cssUnitRegex.test(s) || validator.isValidVariable(s);
83
+ },
84
+ isValidBackgroundPosition: function (s) {
85
+ if (s === 'inherit')
86
+ return true;
87
+
88
+ var parts = s.split(' ');
89
+ for (var i = 0, l = parts.length; i < l; i++) {
90
+ if (parts[i] === '')
91
+ continue;
92
+ if (validator.isValidBackgroundPositionPart(parts[i]) || validator.isValidVariable(parts[i]))
93
+ continue;
94
+
95
+ return false;
96
+ }
97
+
98
+ return true;
99
+ },
100
+ isValidBackgroundSizePart: function(s) {
101
+ return backgroundSizeKeywords.indexOf(s) >= 0 || cssUnitRegex.test(s) || validator.isValidVariable(s);
102
+ },
103
+ isValidBackgroundPositionAndSize: function(s) {
104
+ if (s.indexOf('/') < 0)
105
+ return false;
106
+
107
+ var twoParts = new Splitter('/').split(s);
108
+ return validator.isValidBackgroundSizePart(twoParts.pop()) && validator.isValidBackgroundPositionPart(twoParts.pop());
109
+ },
110
+ isValidListStyleType: function (s) {
111
+ return listStyleTypeKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
112
+ },
113
+ isValidListStylePosition: function (s) {
114
+ return listStylePositionKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
115
+ },
116
+ isValidOutlineColor: function (s) {
117
+ return s === 'invert' || validator.isValidColor(s) || validator.isValidVendorPrefixedValue(s);
118
+ },
119
+ isValidOutlineStyle: function (s) {
120
+ return outlineStyleKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
121
+ },
122
+ isValidOutlineWidth: function (s) {
123
+ return validator.isValidUnit(s) || widthKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
124
+ },
125
+ isValidVendorPrefixedValue: function (s) {
126
+ return /^-([A-Za-z0-9]|-)*$/gi.test(s);
127
+ },
128
+ areSameFunction: function (a, b) {
129
+ if (!validator.isValidFunction(a) || !validator.isValidFunction(b))
130
+ return false;
131
+
132
+ var f1name = a.substring(0, a.indexOf('('));
133
+ var f2name = b.substring(0, b.indexOf('('));
134
+
135
+ return f1name === f2name;
136
+ }
137
+ };
138
+
139
+ return validator;
140
+ })();