mirror of
https://github.com/simple-icons/simple-icons.git
synced 2025-01-25 01:32:58 +02:00
Memoize functions in SVG linting (#9233)
This commit is contained in:
parent
d690e11c4d
commit
8abcd9c8b9
@ -31,6 +31,7 @@ const negativeZerosRegexp = /-0(?=[^\.]|[\s\d\w]|$)/g;
|
|||||||
const svgPathRegexp = /^[Mm][MmZzLlHhVvCcSsQqTtAaEe0-9\-,. ]+$/;
|
const svgPathRegexp = /^[Mm][MmZzLlHhVvCcSsQqTtAaEe0-9\-,. ]+$/;
|
||||||
|
|
||||||
const iconSize = 24;
|
const iconSize = 24;
|
||||||
|
const iconTargetCenter = iconSize / 2;
|
||||||
const iconFloatPrecision = 3;
|
const iconFloatPrecision = 3;
|
||||||
const iconMaxFloatPrecision = 5;
|
const iconMaxFloatPrecision = 5;
|
||||||
const iconTolerance = 0.001;
|
const iconTolerance = 0.001;
|
||||||
@ -117,6 +118,27 @@ const maybeShortenedWithEllipsis = (str) => {
|
|||||||
return str.length > 20 ? `${str.substring(0, 20)}...` : str;
|
return str.length > 20 ? `${str.substring(0, 20)}...` : str;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memoize a function which accepts a single argument.
|
||||||
|
* A second argument can be passed to be used as key.
|
||||||
|
*/
|
||||||
|
const memoize = (func) => {
|
||||||
|
const results = {};
|
||||||
|
|
||||||
|
return (arg, defaultKey = null) => {
|
||||||
|
const key = defaultKey || arg;
|
||||||
|
|
||||||
|
if (!results[key]) {
|
||||||
|
results[key] = func(arg);
|
||||||
|
}
|
||||||
|
return results[key];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIconPath = memoize(($icon, filepath) => $icon.find('path').attr('d'));
|
||||||
|
const getIconPathSegments = memoize((iconPath) => parsePath(iconPath));
|
||||||
|
const getIconPathBbox = memoize((iconPath) => svgPathBbox(iconPath));
|
||||||
|
|
||||||
if (updateIgnoreFile) {
|
if (updateIgnoreFile) {
|
||||||
process.on('exit', () => {
|
process.on('exit', () => {
|
||||||
// ensure object output order is consistent due to async svglint processing
|
// ensure object output order is consistent due to async svglint processing
|
||||||
@ -345,15 +367,15 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(reporter, $) => {
|
(reporter, $, ast, filepath) => {
|
||||||
reporter.name = 'icon-size';
|
reporter.name = 'icon-size';
|
||||||
|
|
||||||
const iconPath = $.find('path').attr('d');
|
const iconPath = getIconPath($, filepath);
|
||||||
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) {
|
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [minX, minY, maxX, maxY] = svgPathBbox(iconPath);
|
const [minX, minY, maxX, maxY] = getIconPathBbox(iconPath);
|
||||||
const width = +(maxX - minX).toFixed(iconFloatPrecision);
|
const width = +(maxX - minX).toFixed(iconFloatPrecision);
|
||||||
const height = +(maxY - minY).toFixed(iconFloatPrecision);
|
const height = +(maxY - minY).toFixed(iconFloatPrecision);
|
||||||
|
|
||||||
@ -374,11 +396,11 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(reporter, $, ast) => {
|
(reporter, $, ast, filepath) => {
|
||||||
reporter.name = 'icon-precision';
|
reporter.name = 'icon-precision';
|
||||||
|
|
||||||
const iconPath = $.find('path').attr('d');
|
const iconPath = getIconPath($, filepath);
|
||||||
const segments = parsePath(iconPath);
|
const segments = getIconPathSegments(iconPath);
|
||||||
|
|
||||||
for (const segment of segments) {
|
for (const segment of segments) {
|
||||||
const precisionMax = Math.max(
|
const precisionMax = Math.max(
|
||||||
@ -404,12 +426,11 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(reporter, $, ast) => {
|
(reporter, $, ast, filepath) => {
|
||||||
reporter.name = 'ineffective-segments';
|
reporter.name = 'ineffective-segments';
|
||||||
|
|
||||||
const iconPath = $.find('path').attr('d');
|
const iconPath = getIconPath($, filepath);
|
||||||
|
const segments = getIconPathSegments(iconPath);
|
||||||
const segments = parsePath(iconPath);
|
|
||||||
const absSegments = svgpath(iconPath).abs().unshort().segments;
|
const absSegments = svgpath(iconPath).abs().unshort().segments;
|
||||||
|
|
||||||
const lowerMovementCommands = ['m', 'l'];
|
const lowerMovementCommands = ['m', 'l'];
|
||||||
@ -625,17 +646,15 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(reporter, $, ast) => {
|
(reporter, $, ast, filepath) => {
|
||||||
reporter.name = 'collinear-segments';
|
reporter.name = 'collinear-segments';
|
||||||
|
|
||||||
const iconPath = $.find('path').attr('d');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts collinear coordinates from SVG path straight lines
|
* Extracts collinear coordinates from SVG path straight lines
|
||||||
* (does not extracts collinear coordinates from curves).
|
* (does not extracts collinear coordinates from curves).
|
||||||
**/
|
**/
|
||||||
const getCollinearSegments = (iconPath) => {
|
const getCollinearSegments = (iconPath) => {
|
||||||
const segments = parsePath(iconPath),
|
const segments = getIconPathSegments(iconPath),
|
||||||
collinearSegments = [],
|
collinearSegments = [],
|
||||||
straightLineCommands = 'HhVvLlMm';
|
straightLineCommands = 'HhVvLlMm';
|
||||||
|
|
||||||
@ -792,8 +811,12 @@ export default {
|
|||||||
return collinearSegments;
|
return collinearSegments;
|
||||||
};
|
};
|
||||||
|
|
||||||
const collinearSegments = getCollinearSegments(iconPath),
|
const iconPath = getIconPath($, filepath),
|
||||||
pathDIndex = getPathDIndex(ast.source);
|
collinearSegments = getCollinearSegments(iconPath);
|
||||||
|
if (collinearSegments.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pathDIndex = getPathDIndex(ast.source);
|
||||||
for (const segment of collinearSegments) {
|
for (const segment of collinearSegments) {
|
||||||
let errorMsg = `Collinear segment "${iconPath.substring(
|
let errorMsg = `Collinear segment "${iconPath.substring(
|
||||||
segment.start,
|
segment.start,
|
||||||
@ -827,10 +850,10 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(reporter, $, ast) => {
|
(reporter, $, ast, filepath) => {
|
||||||
reporter.name = 'negative-zeros';
|
reporter.name = 'negative-zeros';
|
||||||
|
|
||||||
const iconPath = $.find('path').attr('d');
|
const iconPath = getIconPath($, filepath);
|
||||||
|
|
||||||
// Find negative zeros inside path
|
// Find negative zeros inside path
|
||||||
const negativeZeroMatches = Array.from(
|
const negativeZeroMatches = Array.from(
|
||||||
@ -853,27 +876,26 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(reporter, $) => {
|
(reporter, $, ast, filepath) => {
|
||||||
reporter.name = 'icon-centered';
|
reporter.name = 'icon-centered';
|
||||||
|
|
||||||
const iconPath = $.find('path').attr('d');
|
const iconPath = getIconPath($, filepath);
|
||||||
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) {
|
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [minX, minY, maxX, maxY] = svgPathBbox(iconPath);
|
const [minX, minY, maxX, maxY] = getIconPathBbox(iconPath);
|
||||||
const targetCenter = iconSize / 2;
|
|
||||||
const centerX = +((minX + maxX) / 2).toFixed(iconFloatPrecision);
|
const centerX = +((minX + maxX) / 2).toFixed(iconFloatPrecision);
|
||||||
const devianceX = centerX - targetCenter;
|
const devianceX = centerX - iconTargetCenter;
|
||||||
const centerY = +((minY + maxY) / 2).toFixed(iconFloatPrecision);
|
const centerY = +((minY + maxY) / 2).toFixed(iconFloatPrecision);
|
||||||
const devianceY = centerY - targetCenter;
|
const devianceY = centerY - iconTargetCenter;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
Math.abs(devianceX) > iconTolerance ||
|
Math.abs(devianceX) > iconTolerance ||
|
||||||
Math.abs(devianceY) > iconTolerance
|
Math.abs(devianceY) > iconTolerance
|
||||||
) {
|
) {
|
||||||
reporter.error(
|
reporter.error(
|
||||||
`<path> must be centered at (${targetCenter}, ${targetCenter});` +
|
`<path> must be centered at (${iconTargetCenter}, ${iconTargetCenter});` +
|
||||||
` the center is currently (${centerX}, ${centerY})`,
|
` the center is currently (${centerX}, ${centerY})`,
|
||||||
);
|
);
|
||||||
if (updateIgnoreFile) {
|
if (updateIgnoreFile) {
|
||||||
@ -881,16 +903,16 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(reporter, $, ast) => {
|
(reporter, $, ast, filepath) => {
|
||||||
reporter.name = 'path-format';
|
reporter.name = 'path-format';
|
||||||
|
|
||||||
const iconPath = $.find('path').attr('d');
|
const iconPath = getIconPath($, filepath);
|
||||||
|
|
||||||
if (!svgPathRegexp.test(iconPath)) {
|
if (!svgPathRegexp.test(iconPath)) {
|
||||||
let errorMsg = 'Invalid path format',
|
let errorMsg = 'Invalid path format',
|
||||||
reason;
|
reason;
|
||||||
|
|
||||||
if (!/^[Mm]/.test(iconPath)) {
|
if (!iconPath.startsWith('M') && !iconPath.startsWith('m')) {
|
||||||
// doesn't start with moveto
|
// doesn't start with moveto
|
||||||
reason =
|
reason =
|
||||||
'should start with "moveto" command ("M" or "m"),' +
|
'should start with "moveto" command ("M" or "m"),' +
|
||||||
@ -917,8 +939,7 @@ export default {
|
|||||||
if (invalidCharactersMsgs.length > 0) {
|
if (invalidCharactersMsgs.length > 0) {
|
||||||
reason = `unexpected character${
|
reason = `unexpected character${
|
||||||
invalidCharactersMsgs.length > 1 ? 's' : ''
|
invalidCharactersMsgs.length > 1 ? 's' : ''
|
||||||
} found`;
|
} found (${invalidCharactersMsgs.join(', ')})`;
|
||||||
reason += ` (${invalidCharactersMsgs.join(', ')})`;
|
|
||||||
reporter.error(`${errorMsg}: ${reason}`);
|
reporter.error(`${errorMsg}: ${reason}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user