@thejaredwilcurt/csslop 0.0.47 → 0.0.49
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.
- package/README.md +3 -3
- package/package.json +4 -4
- package/src/declarations/css-wide-keywords.js +37 -1
- package/src/declarations/fold.js +13 -3
- package/src/declarations/process.js +60 -7
- package/src/index.js +8 -3
- package/src/parser/source-search.js +6 -0
- package/src/rules/custom-properties.js +19 -0
- package/src/rules/normalize.js +34 -1
- package/src/rules/optimize.js +422 -139
- package/src/rules/selectors.js +9 -0
- package/src/rules/stringify.js +138 -76
- package/src/utilities.js +0 -13
- package/src/value/minify.js +138 -37
- package/src/value/numbers.js +154 -0
- package/src/value/tokens.js +78 -20
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Reads the numbers of numeric CSS tokens as exact digits and an exponent, so they can be rewritten in their shortest decimal or scientific spelling without any floating point rounding.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A number taken apart into the pieces its spelling is built from. The digits
|
|
7
|
+
* never start or end with a zero, so the same value always has the same parts.
|
|
8
|
+
*
|
|
9
|
+
* @typedef {object} NumericParts
|
|
10
|
+
* @property {string} sign The sign as written, kept so a `+` that tells the number apart from a neighboring token survives, or an empty string for zero.
|
|
11
|
+
* @property {string} digits The significant digits, or an empty string for zero.
|
|
12
|
+
* @property {number} exponent The power of ten the digits, read as an integer, are multiplied by.
|
|
13
|
+
* @property {string} unit The unit or percent sign that follows the number.
|
|
14
|
+
* @property {boolean} hasExponent Whether the original spelling used scientific notation.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Matches the text of a numeric token, capturing its sign, its coefficient,
|
|
19
|
+
* the exponent of its scientific notation when it has one, and the unit or
|
|
20
|
+
* percent sign written after it. An exponent needs digits after the `e`, so the
|
|
21
|
+
* `e` that starts a unit such as `em` is left to the unit.
|
|
22
|
+
*
|
|
23
|
+
* @type {RegExp}
|
|
24
|
+
*/
|
|
25
|
+
const NUMERIC_TOKEN_PATTERN = /^([+-]?)(\d*\.\d+|\d+)(?:[eE]([+-]?\d+))?(%|[a-zA-Z_-][\w-]*)?$/;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Takes the text of a numeric token apart into its sign, significant digits,
|
|
29
|
+
* exponent, and unit.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} text The text of a number, percentage, or dimension token.
|
|
32
|
+
* @return {NumericParts|null} The parts of the number, or null when the text is not a plain number.
|
|
33
|
+
*/
|
|
34
|
+
function parseNumericParts (text) {
|
|
35
|
+
const match = text.match(NUMERIC_TOKEN_PATTERN);
|
|
36
|
+
if (!match) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const [, sign, coefficient, writtenExponent, unit = ''] = match;
|
|
40
|
+
const decimalIndex = coefficient.indexOf('.');
|
|
41
|
+
const fractionalDigitCount = decimalIndex === -1 ? 0 : coefficient.length - decimalIndex - 1;
|
|
42
|
+
// Leading zeros carry no magnitude, so only the digits after them count
|
|
43
|
+
const unpaddedDigits = coefficient.replace('.', '').replace(/^0+/, '');
|
|
44
|
+
// Trailing zeros only scale the digits before them, so they move into the exponent
|
|
45
|
+
const digits = unpaddedDigits.replace(/0+$/, '');
|
|
46
|
+
const trailingZeroCount = unpaddedDigits.length - digits.length;
|
|
47
|
+
return {
|
|
48
|
+
sign: digits ? sign : '',
|
|
49
|
+
digits,
|
|
50
|
+
exponent: Number(writtenExponent || 0) + trailingZeroCount - fractionalDigitCount,
|
|
51
|
+
unit,
|
|
52
|
+
hasExponent: writtenExponent !== undefined
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Spells a number out in plain decimal notation, without a leading zero before
|
|
58
|
+
* its decimal point.
|
|
59
|
+
*
|
|
60
|
+
* @param {NumericParts} parts The parts of a non-zero number.
|
|
61
|
+
* @return {string} The decimal spelling, without the unit.
|
|
62
|
+
*/
|
|
63
|
+
function formatDecimal (parts) {
|
|
64
|
+
const { sign, digits, exponent } = parts;
|
|
65
|
+
if (exponent >= 0) {
|
|
66
|
+
return sign + digits + '0'.repeat(exponent);
|
|
67
|
+
}
|
|
68
|
+
const decimalIndex = digits.length + exponent;
|
|
69
|
+
if (decimalIndex > 0) {
|
|
70
|
+
return sign + digits.slice(0, decimalIndex) + '.' + digits.slice(decimalIndex);
|
|
71
|
+
}
|
|
72
|
+
return sign + '.' + '0'.repeat(-decimalIndex) + digits;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Spells a number out in scientific notation, keeping every significant digit
|
|
77
|
+
* in an integer mantissa so no decimal point is ever needed.
|
|
78
|
+
*
|
|
79
|
+
* @param {NumericParts} parts The parts of a non-zero number.
|
|
80
|
+
* @return {string} The scientific spelling, without the unit.
|
|
81
|
+
*/
|
|
82
|
+
function formatScientific (parts) {
|
|
83
|
+
if (parts.exponent === 0) {
|
|
84
|
+
return parts.sign + parts.digits;
|
|
85
|
+
}
|
|
86
|
+
return parts.sign + parts.digits + 'e' + parts.exponent;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Spells a number in the shortest way its grammar allows. Plain decimal
|
|
91
|
+
* notation wins a tie, since it is the more familiar spelling.
|
|
92
|
+
*
|
|
93
|
+
* @param {NumericParts} parts The parts of a non-zero number.
|
|
94
|
+
* @param {boolean} allowsScientific Whether the number may be written in scientific notation.
|
|
95
|
+
* @return {string} The shortest spelling, unit included.
|
|
96
|
+
*/
|
|
97
|
+
function formatShortestNumber (parts, allowsScientific) {
|
|
98
|
+
const decimal = formatDecimal(parts);
|
|
99
|
+
if (!allowsScientific) {
|
|
100
|
+
return decimal + parts.unit;
|
|
101
|
+
}
|
|
102
|
+
const scientific = formatScientific(parts);
|
|
103
|
+
const shortest = scientific.length < decimal.length ? scientific : decimal;
|
|
104
|
+
return shortest + parts.unit;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Rewrites the text of a numeric token in its shortest spelling. A number
|
|
109
|
+
* written without an exponent only changes when the new spelling is shorter,
|
|
110
|
+
* while one written with an exponent is always normalized, which lowercases
|
|
111
|
+
* the `e`, drops a `+` sign, and folds trailing zeros into the exponent.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} text The text of a number, percentage, or dimension token.
|
|
114
|
+
* @param {boolean} allowsScientific Whether the number may be written in scientific notation.
|
|
115
|
+
* @return {string} The shortest equivalent text.
|
|
116
|
+
*/
|
|
117
|
+
function shortenNumericText (text, allowsScientific) {
|
|
118
|
+
const parts = parseNumericParts(text);
|
|
119
|
+
if (!parts || !parts.digits) {
|
|
120
|
+
return text;
|
|
121
|
+
}
|
|
122
|
+
if (parts.hasExponent && !allowsScientific) {
|
|
123
|
+
return text;
|
|
124
|
+
}
|
|
125
|
+
const shortest = formatShortestNumber(parts, allowsScientific);
|
|
126
|
+
if (parts.hasExponent || shortest.length < text.length) {
|
|
127
|
+
return shortest;
|
|
128
|
+
}
|
|
129
|
+
return text;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Scales a number by a power of ten by moving its exponent, which is exact for
|
|
134
|
+
* any number of digits.
|
|
135
|
+
*
|
|
136
|
+
* @param {NumericParts} parts The parts of the number.
|
|
137
|
+
* @param {number} power The power of ten to multiply by.
|
|
138
|
+
* @param {string} unit The unit the scaled number is written with.
|
|
139
|
+
* @return {NumericParts} The parts of the scaled number.
|
|
140
|
+
*/
|
|
141
|
+
function scaleByPowerOfTen (parts, power, unit) {
|
|
142
|
+
return {
|
|
143
|
+
...parts,
|
|
144
|
+
exponent: parts.exponent + power,
|
|
145
|
+
unit
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export {
|
|
150
|
+
formatShortestNumber,
|
|
151
|
+
parseNumericParts,
|
|
152
|
+
scaleByPowerOfTen,
|
|
153
|
+
shortenNumericText
|
|
154
|
+
};
|
package/src/value/tokens.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { tokenizeCssValue } from '../parser/tokenizer.js';
|
|
6
6
|
|
|
7
|
+
import { shortenNumericText } from './numbers.js';
|
|
8
|
+
|
|
7
9
|
/**
|
|
8
10
|
* One token of a value, as the tokenizer reads it.
|
|
9
11
|
*
|
|
@@ -142,36 +144,92 @@ function elideRedundantSeparators (value, elidesAfterSelfEndingTokens = true) {
|
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
/**
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
+
* Properties whose whole value is a unitless `<number>` rather than an
|
|
148
|
+
* `<integer>`, so a number written directly in them may use scientific
|
|
149
|
+
* notation.
|
|
150
|
+
*
|
|
151
|
+
* @type {Set<string>}
|
|
152
|
+
*/
|
|
153
|
+
const NUMBER_VALUED_PROPERTIES = new Set([
|
|
154
|
+
'animation-iteration-count',
|
|
155
|
+
'fill-opacity',
|
|
156
|
+
'flex-grow',
|
|
157
|
+
'flex-shrink',
|
|
158
|
+
'flood-opacity',
|
|
159
|
+
'opacity',
|
|
160
|
+
'stop-opacity',
|
|
161
|
+
'stroke-miterlimit',
|
|
162
|
+
'stroke-opacity',
|
|
163
|
+
'tab-size'
|
|
164
|
+
]);
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Functions whose arguments are unitless `<number>`s rather than `<integer>`s,
|
|
168
|
+
* so the numbers written directly inside them may use scientific notation.
|
|
169
|
+
*
|
|
170
|
+
* @type {Set<string>}
|
|
171
|
+
*/
|
|
172
|
+
const NUMBER_ARGUMENT_FUNCTIONS = new Set([
|
|
173
|
+
'matrix',
|
|
174
|
+
'matrix3d',
|
|
175
|
+
'scale',
|
|
176
|
+
'scale3d',
|
|
177
|
+
'scalex',
|
|
178
|
+
'scaley',
|
|
179
|
+
'scalez'
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Reports whether a unitless number token sits where only a `<number>` is
|
|
184
|
+
* accepted, either directly in a number-valued property or directly inside a
|
|
185
|
+
* function that takes number arguments.
|
|
186
|
+
*
|
|
187
|
+
* @param {string|null} enclosingFunction The lowercased name of the innermost function holding the token, or null at the top level.
|
|
188
|
+
* @param {string} property The lowercased property the value belongs to.
|
|
189
|
+
* @return {boolean} Whether the number may use scientific notation.
|
|
190
|
+
*/
|
|
191
|
+
function isNumberTypedPosition (enclosingFunction, property) {
|
|
192
|
+
if (enclosingFunction === null) {
|
|
193
|
+
return NUMBER_VALUED_PROPERTIES.has(property);
|
|
194
|
+
}
|
|
195
|
+
return NUMBER_ARGUMENT_FUNCTIONS.has(enclosingFunction);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Shortens the numeric tokens of a value into their shortest spelling,
|
|
200
|
+
* including scientific notation. Dimensions and percentages are always
|
|
201
|
+
* `<number>` based, but a unitless number is only shortened where its grammar
|
|
202
|
+
* is known to take a `<number>`, because `<integer>` grammars reject
|
|
147
203
|
* scientific notation.
|
|
148
204
|
*
|
|
149
|
-
* @param {string} value
|
|
150
|
-
* @
|
|
205
|
+
* @param {string} value The CSS value.
|
|
206
|
+
* @param {string} [property] The property the value belongs to.
|
|
207
|
+
* @return {string} The value with shorter numeric spellings.
|
|
151
208
|
*/
|
|
152
|
-
function shortenDimensionNumbers (value) {
|
|
209
|
+
function shortenDimensionNumbers (value, property = '') {
|
|
210
|
+
const normalizedProperty = property.toLowerCase();
|
|
211
|
+
const functionStack = [];
|
|
153
212
|
return tokenizeCssValue(value).map((token) => {
|
|
154
|
-
if (token.type
|
|
213
|
+
if (token.type === 'function') {
|
|
214
|
+
functionStack.push(token.text.slice(0, -1).toLowerCase());
|
|
155
215
|
return token.text;
|
|
156
216
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (!match) {
|
|
217
|
+
if (token.text === '(') {
|
|
218
|
+
functionStack.push('');
|
|
160
219
|
return token.text;
|
|
161
220
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const fractionalDigits = decimalIndex === -1 ? 0 : coefficient.length - decimalIndex - 1;
|
|
165
|
-
const digits = coefficient.replace('.', '');
|
|
166
|
-
// Leading zeros carry no magnitude; trailing zeros move into the exponent.
|
|
167
|
-
const significantDigits = digits.replace(/^0+/, '').replace(/0+$/, '');
|
|
168
|
-
if (!significantDigits) {
|
|
221
|
+
if (token.text === ')') {
|
|
222
|
+
functionStack.pop();
|
|
169
223
|
return token.text;
|
|
170
224
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
225
|
+
if (token.type === 'dimension' || token.type === 'percentage') {
|
|
226
|
+
return shortenNumericText(token.text, true);
|
|
227
|
+
}
|
|
228
|
+
const enclosingFunction = functionStack.length ? functionStack[functionStack.length - 1] : null;
|
|
229
|
+
if (token.type === 'number' && isNumberTypedPosition(enclosingFunction, normalizedProperty)) {
|
|
230
|
+
return shortenNumericText(token.text, true);
|
|
231
|
+
}
|
|
232
|
+
return token.text;
|
|
175
233
|
}).join('');
|
|
176
234
|
}
|
|
177
235
|
|