@thejaredwilcurt/csslop 0.0.49 → 0.0.51

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 (57) hide show
  1. package/README.md +0 -1
  2. package/package.json +2 -2
  3. package/src/declarations/border.js +1 -1
  4. package/src/declarations/process.js +155 -1
  5. package/src/index.js +7 -7
  6. package/src/rules/optimize/at-rules.js +113 -0
  7. package/src/rules/optimize/declaration-writes.js +70 -0
  8. package/src/rules/optimize/empty-rules.js +51 -0
  9. package/src/rules/optimize/expand-nesting.js +236 -0
  10. package/src/rules/optimize/factor-parents.js +343 -0
  11. package/src/rules/optimize/index.js +71 -0
  12. package/src/rules/optimize/merge-declarations.js +199 -0
  13. package/src/rules/optimize/merge-nested-rules.js +117 -0
  14. package/src/rules/optimize/merge-selectors.js +252 -0
  15. package/src/rules/optimize/nest-rules.js +202 -0
  16. package/src/rules/optimize/override-index.js +264 -0
  17. package/src/rules/optimize/prune-overridden.js +267 -0
  18. package/src/rules/optimize/selector-normalization.js +60 -0
  19. package/src/rules/optimize/specificity.js +280 -0
  20. package/src/{position-try.js → rules/position-try.js} +1 -1
  21. package/src/rules/stringify.js +13 -0
  22. package/src/utilities.js +14 -0
  23. package/src/value/border.js +85 -0
  24. package/src/value/{colors.js → color/conversion.js} +3 -2
  25. package/src/value/color/functions.js +463 -0
  26. package/src/value/{light-dark.js → color/light-dark.js} +1 -1
  27. package/src/value/{color-mix.js → color/mix.js} +3 -3
  28. package/src/value/{relative-colors.js → color/relative.js} +3 -3
  29. package/src/value/color/shorten.js +93 -0
  30. package/src/value/gradients.js +1 -1
  31. package/src/value/hyphenate-limit-chars.js +72 -0
  32. package/src/value/initial-keywords.js +128 -0
  33. package/src/value/lengths.js +55 -0
  34. package/src/value/math/evaluate.js +36 -0
  35. package/src/value/math/fold.js +315 -0
  36. package/src/value/math/format.js +230 -0
  37. package/src/value/math/functions.js +302 -0
  38. package/src/value/math/index.js +219 -0
  39. package/src/value/math/parser.js +301 -0
  40. package/src/value/math/property-ranges.js +174 -0
  41. package/src/value/math/rewrite.js +222 -0
  42. package/src/value/math/tokenizer.js +160 -0
  43. package/src/value/math/unit-totals.js +194 -0
  44. package/src/value/minify.js +127 -1669
  45. package/src/value/numbers.js +28 -0
  46. package/src/value/positions.js +152 -0
  47. package/src/value/property-optimizations.js +485 -0
  48. package/src/value/segments.js +74 -0
  49. package/src/value/{svg-data-uri.js → svg/data-uri.js} +1 -1
  50. package/src/value/{svg-markup.js → svg/markup.js} +1 -1
  51. package/src/value/time.js +75 -0
  52. package/src/value/urls.js +242 -0
  53. package/src/value/whitespace.js +88 -0
  54. package/src/rules/optimize.js +0 -2314
  55. package/src/value/math-expression.js +0 -1156
  56. package/src/value/math.js +0 -886
  57. /package/src/value/{named-colors.js → color/named.js} +0 -0
@@ -0,0 +1,160 @@
1
+ /**
2
+ * @file Splits a CSS math function into the numbers, identifiers, function names, operators, and punctuation it is written from.
3
+ */
4
+
5
+ import {
6
+ PERCENTAGE,
7
+ UNITLESS
8
+ } from './unit-totals.js';
9
+
10
+ /**
11
+ * Matches a CSS number where the scanner stands: the digits it is written
12
+ * from, together with the fraction and the exponent it may be written with.
13
+ * A sign is left to the parser, which tells the sign of a number from an
14
+ * operator by whether whitespace stands between the two.
15
+ *
16
+ * @type {RegExp}
17
+ */
18
+ const NUMBER_AT_INDEX = /(?:\d+\.\d+|\.\d+|\d+)(?:e[+-]?\d+)?/iy;
19
+
20
+ /**
21
+ * Matches a CSS identifier where the scanner stands: a name of letters,
22
+ * digits, underscores, and hyphens that does not begin with a digit. A numeric
23
+ * constant, a function name, and a keyword argument are all written as
24
+ * identifiers.
25
+ *
26
+ * @type {RegExp}
27
+ */
28
+ const IDENTIFIER_AT_INDEX = /[a-z_][a-z0-9_-]*/iy;
29
+
30
+ /**
31
+ * Matches the unit written against a number where the scanner stands. Every
32
+ * CSS unit is named in letters alone, so a name holding anything else is a
33
+ * token of its own that a number cannot carry.
34
+ *
35
+ * @type {RegExp}
36
+ */
37
+ const UNIT_AT_INDEX = /[a-z]+/iy;
38
+
39
+ /**
40
+ * The characters CSS counts as whitespace, which separates the tokens of a
41
+ * calculation and tells an operator apart from the sign of a number.
42
+ *
43
+ * @type {Set<string>}
44
+ */
45
+ const WHITESPACE_CHARACTERS = new Set([' ', '\t', '\n', '\r', '\f']);
46
+
47
+ /**
48
+ * The characters that punctuate a calculation, and the kind of token each of
49
+ * them stands for.
50
+ *
51
+ * @type {Map<string, string>}
52
+ */
53
+ const PUNCTUATION_TOKEN_TYPES = new Map([
54
+ ['(', 'open'],
55
+ [')', 'close'],
56
+ [',', 'comma']
57
+ ]);
58
+
59
+ /**
60
+ * The arithmetic operators a calculation is written with.
61
+ *
62
+ * @type {Set<string>}
63
+ */
64
+ const OPERATOR_CHARACTERS = new Set(['+', '-', '*', '/']);
65
+
66
+ /**
67
+ * Reads the unit written directly against the number that ends at the given
68
+ * index, which is a percent sign or an identifier naming the unit. A number
69
+ * with nothing written against it carries no unit.
70
+ *
71
+ * @param {string} text The math function being tokenized.
72
+ * @param {number} index The index just past the number's digits.
73
+ * @return {object} The unit read and the index just past it.
74
+ */
75
+ function readUnitAtIndex (text, index) {
76
+ if (text[index] === PERCENTAGE) {
77
+ return { unit: PERCENTAGE, nextIndex: index + 1 };
78
+ }
79
+ UNIT_AT_INDEX.lastIndex = index;
80
+ const unitMatch = UNIT_AT_INDEX.exec(text);
81
+ if (!unitMatch) {
82
+ return { unit: UNITLESS, nextIndex: index };
83
+ }
84
+ return { unit: unitMatch[0], nextIndex: index + unitMatch[0].length };
85
+ }
86
+
87
+ /**
88
+ * Splits a math function into the tokens it is written from: its numbers with
89
+ * the units they carry, its identifiers, its function names, its operators,
90
+ * and its punctuation. Each token records whether whitespace precedes it,
91
+ * since that is what tells the sign of a number from an operator.
92
+ *
93
+ * @param {string} text The math function as it is written.
94
+ * @return {Array|null} The tokens of the math function, or null when it holds a character no calculation is written with.
95
+ */
96
+ function tokenizeMathExpression (text) {
97
+ const tokens = [];
98
+ let index = 0;
99
+ let precededBySpace = false;
100
+
101
+ while (index < text.length) {
102
+ const character = text[index];
103
+
104
+ if (WHITESPACE_CHARACTERS.has(character)) {
105
+ precededBySpace = true;
106
+ index++;
107
+ continue;
108
+ }
109
+
110
+ NUMBER_AT_INDEX.lastIndex = index;
111
+ const numberMatch = NUMBER_AT_INDEX.exec(text);
112
+ if (numberMatch) {
113
+ const { unit, nextIndex } = readUnitAtIndex(text, index + numberMatch[0].length);
114
+ tokens.push({
115
+ type: 'number',
116
+ number: Number(numberMatch[0]),
117
+ unit,
118
+ precededBySpace
119
+ });
120
+ index = nextIndex;
121
+ precededBySpace = false;
122
+ continue;
123
+ }
124
+
125
+ if (OPERATOR_CHARACTERS.has(character) || PUNCTUATION_TOKEN_TYPES.has(character)) {
126
+ tokens.push({
127
+ type: PUNCTUATION_TOKEN_TYPES.get(character) || 'operator',
128
+ operator: character,
129
+ precededBySpace
130
+ });
131
+ index++;
132
+ precededBySpace = false;
133
+ continue;
134
+ }
135
+
136
+ IDENTIFIER_AT_INDEX.lastIndex = index;
137
+ const identifierMatch = IDENTIFIER_AT_INDEX.exec(text);
138
+ if (!identifierMatch) {
139
+ return null;
140
+ }
141
+ index = index + identifierMatch[0].length;
142
+ // A name written directly against an opening parenthesis calls a function
143
+ const callsFunction = text[index] === '(';
144
+ if (callsFunction) {
145
+ index++;
146
+ }
147
+ tokens.push({
148
+ type: callsFunction ? 'function' : 'identifier',
149
+ name: identifierMatch[0].toLowerCase(),
150
+ precededBySpace
151
+ });
152
+ precededBySpace = false;
153
+ }
154
+
155
+ return tokens;
156
+ }
157
+
158
+ export {
159
+ tokenizeMathExpression
160
+ };
@@ -0,0 +1,194 @@
1
+ /**
2
+ * @file Represents the value of a CSS calculation as totals keyed by unit, and works out the sums, products, and quotients of such values.
3
+ */
4
+
5
+ import { convertAbsoluteLengthToPx } from '../shared.js';
6
+
7
+ /**
8
+ * The unit a plain number is totalled under. A number carries no unit, and the
9
+ * empty string is the unit it does not carry.
10
+ *
11
+ * @type {string}
12
+ */
13
+ const UNITLESS = '';
14
+
15
+ /**
16
+ * The unit a percentage is totalled under. A percentage is only resolved
17
+ * against its basis once the element is laid out, so it is totalled as a unit
18
+ * of its own rather than as the number it is written from.
19
+ *
20
+ * @type {string}
21
+ */
22
+ const PERCENTAGE = '%';
23
+
24
+ /**
25
+ * Creates the totals of a value that states a single quantity, holding the
26
+ * number it is written from under the unit it is written with. An absolute
27
+ * length is held as the pixels it measures, since every length stating the
28
+ * same measurement then totals under the same unit.
29
+ *
30
+ * @param {number} number The number the quantity is written from.
31
+ * @param {string} unit The unit the quantity is written with, or the empty string when it is a plain number.
32
+ * @param {boolean} preserveWrittenQuantity Whether to retain the original spelling for a standalone quantity.
33
+ * @return {Map} The unit totals the quantity states.
34
+ */
35
+ function createUnitTotals (number, unit, preserveWrittenQuantity = false) {
36
+ const lowercasedUnit = unit.toLowerCase();
37
+ const pixels = convertAbsoluteLengthToPx(number, lowercasedUnit);
38
+ let totals;
39
+ if (pixels === null) {
40
+ totals = new Map([[lowercasedUnit, number]]);
41
+ } else {
42
+ totals = new Map([['px', pixels]]);
43
+ }
44
+ if (preserveWrittenQuantity) {
45
+ totals.writtenQuantity = {
46
+ number,
47
+ unit: lowercasedUnit,
48
+ canonicalUnit: pixels === null ? lowercasedUnit : 'px',
49
+ canonicalNumber: pixels === null ? number : pixels
50
+ };
51
+ }
52
+ return totals;
53
+ }
54
+
55
+ /**
56
+ * Reports whether a value states a plain number, which is what an operand has
57
+ * to be to scale or divide the quantity it is written against.
58
+ *
59
+ * @param {Map} value The unit totals the value holds.
60
+ * @return {boolean} Whether the value states a plain number.
61
+ */
62
+ function isPlainNumber (value) {
63
+ return value.size === 1 && value.has(UNITLESS);
64
+ }
65
+
66
+ /**
67
+ * Reads a value as the single quantity it states, which is what a function
68
+ * comparing its arguments needs each of them to be. A value totalling more
69
+ * than one unit is only resolved once its units are resolved against the
70
+ * element, so it states no single number to read.
71
+ *
72
+ * @param {Map} value The unit totals the value holds.
73
+ * @return {object|null} The number and the unit the value states, or null when it states more than one quantity.
74
+ */
75
+ function readSingleQuantity (value) {
76
+ if (value.size !== 1) {
77
+ return null;
78
+ }
79
+ const [[unit, number]] = [...value];
80
+ return { number, unit };
81
+ }
82
+
83
+ /**
84
+ * Scales every quantity a value states by a plain number, which is how a
85
+ * multiplication and a division are worked out.
86
+ *
87
+ * @param {Map} value The unit totals the value holds.
88
+ * @param {number} factor The number every total is scaled by.
89
+ * @return {Map} The scaled unit totals.
90
+ */
91
+ function scaleUnitTotals (value, factor) {
92
+ const scaled = new Map();
93
+ for (const [unit, coefficient] of value) {
94
+ scaled.set(unit, coefficient * factor);
95
+ }
96
+ return scaled;
97
+ }
98
+
99
+ /**
100
+ * Adds one value to another, totalling the quantities they state under the
101
+ * units they are written with. A sum of a plain number and a quantity carrying
102
+ * a unit states nothing a stylesheet can hold, so such a sum has no total to
103
+ * give.
104
+ *
105
+ * @param {Map} left The unit totals the value on the left holds.
106
+ * @param {Map} right The unit totals the value on the right holds.
107
+ * @param {number} sign The direction the right value is added in: 1 to add it, -1 to subtract it.
108
+ * @return {Map|null} The totals of the sum, or null when the two cannot be added.
109
+ */
110
+ function addUnitTotals (left, right, sign) {
111
+ const totals = new Map(left);
112
+ for (const [unit, coefficient] of right) {
113
+ totals.set(unit, (totals.get(unit) || 0) + (coefficient * sign));
114
+ }
115
+ const addsNumberToQuantity = totals.has(UNITLESS) && totals.size > 1;
116
+ if (addsNumberToQuantity) {
117
+ return null;
118
+ }
119
+ return totals;
120
+ }
121
+
122
+ /**
123
+ * Multiplies two values, which CSS only allows when one of them is a plain
124
+ * number: the product of two quantities carrying units measures an area or a
125
+ * rate, and no CSS property takes one.
126
+ *
127
+ * @param {Map} left The unit totals the value on the left holds.
128
+ * @param {Map} right The unit totals the value on the right holds.
129
+ * @return {Map|null} The totals of the product, or null when neither side is a plain number.
130
+ */
131
+ function multiplyUnitTotals (left, right) {
132
+ if (isPlainNumber(right)) {
133
+ return scaleUnitTotals(left, right.get(UNITLESS));
134
+ }
135
+ if (isPlainNumber(left)) {
136
+ return scaleUnitTotals(right, left.get(UNITLESS));
137
+ }
138
+ const leftQuantity = readSingleQuantity(left);
139
+ const rightQuantity = readSingleQuantity(right);
140
+ if (leftQuantity && rightQuantity) {
141
+ if (leftQuantity.unit.startsWith('reciprocal:') && leftQuantity.unit.slice('reciprocal:'.length) === rightQuantity.unit) {
142
+ return createUnitTotals(leftQuantity.number * rightQuantity.number, UNITLESS);
143
+ }
144
+ if (rightQuantity.unit.startsWith('reciprocal:') && rightQuantity.unit.slice('reciprocal:'.length) === leftQuantity.unit) {
145
+ return createUnitTotals(leftQuantity.number * rightQuantity.number, UNITLESS);
146
+ }
147
+ }
148
+ return null;
149
+ }
150
+
151
+ /**
152
+ * Divides a value by another, which CSS only allows when the divisor is a
153
+ * plain number. A division by zero resolves to an infinity that no property
154
+ * takes, so it is left for the stylesheet to state as written.
155
+ *
156
+ * @param {Map} left The unit totals of the value being divided.
157
+ * @param {Map} right The unit totals of the value dividing it.
158
+ * @return {Map|null} The totals of the quotient, or null when the division cannot be worked out.
159
+ */
160
+ function divideUnitTotals (left, right) {
161
+ if (isPlainNumber(right)) {
162
+ const divisor = right.get(UNITLESS);
163
+ if (divisor === 0) {
164
+ return null;
165
+ }
166
+ return scaleUnitTotals(left, 1 / divisor);
167
+ }
168
+ const dividendQuantity = readSingleQuantity(left);
169
+ const divisorQuantity = readSingleQuantity(right);
170
+ if (!dividendQuantity || !divisorQuantity || divisorQuantity.number === 0) {
171
+ return null;
172
+ }
173
+ if (dividendQuantity.unit === divisorQuantity.unit) {
174
+ return createUnitTotals(dividendQuantity.number / divisorQuantity.number, UNITLESS);
175
+ }
176
+ if (dividendQuantity.unit === UNITLESS) {
177
+ return createUnitTotals(
178
+ dividendQuantity.number / divisorQuantity.number,
179
+ 'reciprocal:' + divisorQuantity.unit
180
+ );
181
+ }
182
+ return null;
183
+ }
184
+
185
+ export {
186
+ addUnitTotals,
187
+ createUnitTotals,
188
+ divideUnitTotals,
189
+ multiplyUnitTotals,
190
+ PERCENTAGE,
191
+ readSingleQuantity,
192
+ scaleUnitTotals,
193
+ UNITLESS
194
+ };