Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions src/js/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import { LRUCache } from 'lru-cache';
import { Options } from './typedef';
import { valueToJsonString } from './util';

/* numeric constants */
const MAX_CACHE = 4096;
Expand Down Expand Up @@ -100,15 +99,28 @@ export const createCacheKey = (
opt: Options = {}
): string => {
const { customProperty = {}, dimension = {} } = opt;
let cacheKey = '';
if (
keyData &&
Object.keys(keyData).length &&
typeof customProperty.callback !== 'function' &&
typeof dimension.callback !== 'function'
!keyData ||
Object.keys(keyData).length === 0 ||
typeof customProperty.callback === 'function' ||
typeof dimension.callback === 'function'
) {
keyData.opt = valueToJsonString(opt);
cacheKey = valueToJsonString(keyData);
return '';
}
return cacheKey;
const baseKey = `${keyData.namespace || ''}:${keyData.name || ''}:${keyData.value || ''}`;
const optStr = [
opt.format || '',
opt.colorSpace || '',
opt.colorScheme || '',
opt.currentColor || '',
opt.d50 ? '1' : '0',
opt.nullable ? '1' : '0',
opt.preserveComment ? '1' : '0',
String(opt.delimiter || '')
].join('|');
const customPropStr = Object.keys(customProperty).length
? JSON.stringify(customProperty)
: '';
const dimStr = Object.keys(dimension).length ? JSON.stringify(dimension) : '';
return `${baseKey}::${optStr}::${customPropStr}::${dimStr}`;
};
3 changes: 1 addition & 2 deletions src/js/css-calc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -793,8 +793,7 @@ export const parseTokens = (
const mathFunc = new Set();
let nest = 0;
const res: string[] = [];
while (tokens.length) {
const token = tokens.shift();
for (const token of tokens) {
if (!Array.isArray(token)) {
throw new TypeError(`${token} is not an array.`);
}
Expand Down
85 changes: 32 additions & 53 deletions src/js/css-gradient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,26 @@ const FROM_ANGLE = `from\\s+${DIM_ANGLE}`;
const AT_POSITION = `at\\s+(?:${POS_1}|${POS_2}|${POS_4})`;
const TO_SIDE_CORNER = `to\\s+(?:(?:${L_R})(?:\\s(?:${T_B}))?|(?:${T_B})(?:\\s(?:${L_R}))?)`;
const IN_COLOR_SPACE = `in\\s+(?:${CS_RECT}|${CS_HUE})`;
const LINE_SYNTAX_LINEAR = [
`(?:${DIM_ANGLE}|${TO_SIDE_CORNER})(?:\\s+${IN_COLOR_SPACE})?`,
`${IN_COLOR_SPACE}(?:\\s+(?:${DIM_ANGLE}|${TO_SIDE_CORNER}))?`
].join('|');
const LINE_SYNTAX_RADIAL = [
`(?:${RAD_SHAPE})(?:\\s+(?:${RAD_SIZE}))?(?:\\s+${AT_POSITION})?(?:\\s+${IN_COLOR_SPACE})?`,
`(?:${RAD_SIZE})(?:\\s+(?:${RAD_SHAPE}))?(?:\\s+${AT_POSITION})?(?:\\s+${IN_COLOR_SPACE})?`,
`${AT_POSITION}(?:\\s+${IN_COLOR_SPACE})?`,
`${IN_COLOR_SPACE}(?:\\s+${RAD_SHAPE})(?:\\s+(?:${RAD_SIZE}))?(?:\\s+${AT_POSITION})?`,
`${IN_COLOR_SPACE}(?:\\s+${RAD_SIZE})(?:\\s+(?:${RAD_SHAPE}))?(?:\\s+${AT_POSITION})?`,
`${IN_COLOR_SPACE}(?:\\s+${AT_POSITION})?`
].join('|');
const LINE_SYNTAX_CONIC = [
`${FROM_ANGLE}(?:\\s+${AT_POSITION})?(?:\\s+${IN_COLOR_SPACE})?`,
`${AT_POSITION}(?:\\s+${IN_COLOR_SPACE})?`,
`${IN_COLOR_SPACE}(?:\\s+${FROM_ANGLE})?(?:\\s+${AT_POSITION})?`
].join('|');
const REG_LINE_LINEAR = new RegExp(`^(?:${LINE_SYNTAX_LINEAR})$`);
const REG_LINE_RADIAL = new RegExp(`^(?:${LINE_SYNTAX_RADIAL})$`);
const REG_LINE_CONIC = new RegExp(`^(?:${LINE_SYNTAX_CONIC})$`);

/* type definitions */
/**
Expand Down Expand Up @@ -136,73 +156,32 @@ export const validateGradientLine = (
if (isString(value) && isString(type)) {
value = value.trim();
type = type.trim();
let lineSyntax = '';
const defaultValues = [];
let reg: RegExp | null = null;
let defaultValues: RegExp[] = [];
if (/^(?:repeating-)?linear-gradient$/.test(type)) {
/*
* <linear-gradient-line> = [
* [ <angle> | to <side-or-corner> ] ||
* <color-interpolation-method>
* ]
*/
lineSyntax = [
`(?:${DIM_ANGLE}|${TO_SIDE_CORNER})(?:\\s+${IN_COLOR_SPACE})?`,
`${IN_COLOR_SPACE}(?:\\s+(?:${DIM_ANGLE}|${TO_SIDE_CORNER}))?`
].join('|');
defaultValues.push(/to\s+bottom/);
reg = REG_LINE_LINEAR;
defaultValues = [/to\s+bottom/];
} else if (/^(?:repeating-)?radial-gradient$/.test(type)) {
/*
* <radial-gradient-line> = [
* [ [ <radial-shape> || <radial-size> ]? [ at <position> ]? ] ||
* <color-interpolation-method>]?
*/
lineSyntax = [
`(?:${RAD_SHAPE})(?:\\s+(?:${RAD_SIZE}))?(?:\\s+${AT_POSITION})?(?:\\s+${IN_COLOR_SPACE})?`,
`(?:${RAD_SIZE})(?:\\s+(?:${RAD_SHAPE}))?(?:\\s+${AT_POSITION})?(?:\\s+${IN_COLOR_SPACE})?`,
`${AT_POSITION}(?:\\s+${IN_COLOR_SPACE})?`,
`${IN_COLOR_SPACE}(?:\\s+${RAD_SHAPE})(?:\\s+(?:${RAD_SIZE}))?(?:\\s+${AT_POSITION})?`,
`${IN_COLOR_SPACE}(?:\\s+${RAD_SIZE})(?:\\s+(?:${RAD_SHAPE}))?(?:\\s+${AT_POSITION})?`,
`${IN_COLOR_SPACE}(?:\\s+${AT_POSITION})?`
].join('|');
defaultValues.push(/ellipse/, /farthest-corner/, /at\s+center/);
reg = REG_LINE_RADIAL;
defaultValues = [/ellipse/, /farthest-corner/, /at\s+center/];
} else if (/^(?:repeating-)?conic-gradient$/.test(type)) {
/*
* <conic-gradient-line> = [
* [ [ from <angle> ]? [ at <position> ]? ] ||
* <color-interpolation-method>
* ]
*/
lineSyntax = [
`${FROM_ANGLE}(?:\\s+${AT_POSITION})?(?:\\s+${IN_COLOR_SPACE})?`,
`${AT_POSITION}(?:\\s+${IN_COLOR_SPACE})?`,
`${IN_COLOR_SPACE}(?:\\s+${FROM_ANGLE})?(?:\\s+${AT_POSITION})?`
].join('|');
defaultValues.push(/at\s+center/);
reg = REG_LINE_CONIC;
defaultValues = [/at\s+center/];
}
if (lineSyntax) {
const reg = new RegExp(`^(?:${lineSyntax})$`);
if (reg) {
const valid = reg.test(value);
if (valid) {
let line = value;
for (const defaultValue of defaultValues) {
line = line.replace(defaultValue, '');
}
line = line.replace(/\s{2,}/g, ' ').trim();
return {
line,
valid
};
return { line, valid };
}
return {
valid,
line: value
};
return { valid, line: value };
}
}
return {
line: value,
valid: false
};
return { line: value, valid: false };
};

/**
Expand Down
10 changes: 5 additions & 5 deletions src/js/relative-color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,7 @@ export function resolveColorChannels(
let nest = 0;
let func = '';
let precededPct = false;
while (tokens.length) {
const token = tokens.shift();
for (const token of tokens) {
if (!Array.isArray(token)) {
throw new TypeError(`${token} is not an array.`);
}
Expand Down Expand Up @@ -390,8 +389,9 @@ export function extractOriginColor(
const tokens = tokenize({ css: restValue });
const originColor: string[] = [];
let nest = 0;
while (tokens.length) {
const [type, tokenValue] = tokens.shift() as [TokenType, string];
let tokenIndex = 0;
for (const [type, tokenValue] of tokens) {
tokenIndex++;
switch (type) {
case FUNC:
case PAREN_OPEN: {
Expand Down Expand Up @@ -438,7 +438,7 @@ export function extractOriginColor(
setCache(cacheKey, null);
return resolvedOriginColor;
}
const channelValues = resolveColorChannels(tokens, opt);
const channelValues = resolveColorChannels(tokens.slice(tokenIndex), opt);
if (channelValues instanceof NullObject) {
setCache(cacheKey, null);
return channelValues;
Expand Down
67 changes: 12 additions & 55 deletions src/js/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const {
Delim: DELIM,
EOF,
Function: FUNC,
Ident: IDENT,
OpenParen: PAREN_OPEN,
Whitespace: W_SPACE
} = TokenType;
Expand All @@ -32,6 +31,7 @@ const DEG_HALF = 180;

/* regexp */
const REG_COLOR = new RegExp(`^(?:${SYN_COLOR_TYPE})$`);
const REG_DIMENSION = /^([+-]?(?:\d+(?:\.\d+)?|\.\d+)(?:e[+-]?\d+)?)([a-z]*)$/i;
const REG_FN_COLOR =
/^(?:(?:ok)?l(?:ab|ch)|color(?:-mix)?|hsla?|hwb|rgba?|var)\(/;
const REG_MIX = new RegExp(SYN_MIX);
Expand Down Expand Up @@ -78,8 +78,7 @@ export const splitValue = (value: string, opt: Options = {}): string[] => {
let nest = 0;
let str = '';
const res: string[] = [];
while (tokens.length) {
const [type, value] = tokens.shift() as [TokenType, string];
for (const [type, value] of tokens) {
switch (type) {
case COMMA: {
if (regDelimiter.test(value)) {
Expand Down Expand Up @@ -173,15 +172,8 @@ export const extractDashedIdent = (value: string): string[] => {
if (cachedResult instanceof CacheItem) {
return cachedResult.item as string[];
}
const tokens = tokenize({ css: value });
const items = new Set();
while (tokens.length) {
const [type, value] = tokens.shift() as [TokenType, string];
if (type === IDENT && value.startsWith('--')) {
items.add(value);
}
}
const res = [...items] as string[];
const matches = value.match(/--[\w-]+/g);
const res = matches ? [...new Set(matches)] : [];
setCache(cacheKey, res);
return res;
};
Expand Down Expand Up @@ -220,41 +212,6 @@ export const isColor = (value: unknown, opt: Options = {}): boolean => {
return false;
};

/**
* value to JSON string
* @param value - CSS value
* @param [func] - stringify function
* @returns stringified value in JSON notation
*/
export const valueToJsonString = (
value: unknown,
func: boolean = false
): string => {
if (typeof value === 'undefined') {
return '';
}
const res = JSON.stringify(value, (_key, val) => {
let replacedValue;
if (typeof val === 'undefined') {
replacedValue = null;
} else if (typeof val === 'function') {
if (func) {
replacedValue = val.toString().replace(/\s/g, '').substring(0, HEX);
} else {
replacedValue = val.name;
}
} else if (val instanceof Map || val instanceof Set) {
replacedValue = [...val];
} else if (typeof val === 'bigint') {
replacedValue = val.toString();
} else {
replacedValue = val;
}
return replacedValue;
});
return res;
};

/**
* round to specified precision
* @param value - numeric value
Expand Down Expand Up @@ -469,14 +426,14 @@ export const isAbsoluteSizeOrLength = (
*/
export const isAbsoluteFontSize = (css: unknown): boolean => {
if (isString(css)) {
const [token] = tokenize({ css });
if (Array.isArray(token)) {
const [, , , , detail = {}] = token;
const { unit, value } = detail as {
unit: string;
value: number;
};
return isAbsoluteSizeOrLength(value, unit);
const str = css.trim();
if (isAbsoluteSizeOrLength(str, undefined)) {
return true;
}
const match = str.match(REG_DIMENSION);
if (match) {
const [, value, unit] = match;
return isAbsoluteSizeOrLength(Number(value), unit || undefined);
}
}
return false;
Expand Down
35 changes: 34 additions & 1 deletion test/cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,40 @@ describe('create cache key', () => {
foo: 'foo',
bar: 'bar'
});
assert.strictEqual(res, '{"foo":"foo","bar":"bar","opt":"{}"}', 'result');
assert.strictEqual(res, '::::||||0|0|0|::::', 'result');
});

it('should get value', () => {
const res = func(
{
namespace: 'foo',
name: 'bar',
value: 'baz'
},
{
format: 'computedValue',
colorSpace: 'srgb',
colorScheme: 'normal',
currentColor: 'black',
d50: false,
nullable: false,
preserveComment: true,
delimiter: ' ',
customProperty: {
'--foo': 'foo',
'--bar': 'bar'
},
dimension: {
em: 12,
rem: 16
}
}
);
assert.strictEqual(
res,
'foo:bar:baz::computedValue|srgb|normal|black|0|0|1| ::{"--foo":"foo","--bar":"bar"}::{"em":12,"rem":16}',
'result'
);
});

it('should get empty string', () => {
Expand Down
Loading
Loading