1
0
mirror of https://github.com/simple-icons/simple-icons.git synced 2024-12-16 01:10:30 +02:00

Fix linting error and refactor .svglintrc.mjs (#9195)

This commit is contained in:
Álvaro Mondéjar 2023-08-03 19:27:20 -06:00 committed by GitHub
parent fe7d231142
commit 720a0e4d53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 221 additions and 211 deletions

View File

@ -4,8 +4,5 @@
}, },
"icon-size": { "icon-size": {
"M22.915 8.321c-.642-.997-1.542-1.879-2.672-2.624-2.185-1.436-5.056-2.227-8.084-2.227-1.012 0-2.009.088-2.976.262a9.84 9.84 0 0 0-2.046-1.509C4.378.848 1.947 1.361.719 1.802a.59.59 0 0 0-.229.964c.866.894 2.299 2.66 1.946 4.267C1.067 8.431.324 10.117.324 11.872c0 1.789.742 3.475 2.112 4.873.352 1.607-1.081 3.374-1.947 4.268a.589.589 0 0 0 .229.963c1.228.442 3.659.955 6.418-.421a9.892 9.892 0 0 0 2.046-1.509c.968.174 1.964.262 2.976.262 3.029 0 5.9-.79 8.084-2.226 1.131-.744 2.031-1.626 2.672-2.624.715-1.11 1.077-2.306 1.077-3.552.001-1.279-.361-2.473-1.076-3.585zm-10.881 9.916c-1.309 0-2.558-.169-3.696-.474l-.832.8A7.609 7.609 0 0 1 5.972 19.7a6.033 6.033 0 0 1-2.17.613c.041-.073.078-.147.117-.221.833-1.531 1.059-2.907.674-4.128-1.363-1.071-2.181-2.442-2.181-3.935 0-3.427 4.308-6.206 9.621-6.206 5.313 0 9.622 2.779 9.622 6.206.001 3.429-4.307 6.208-9.621 6.208zM8.85 12.01c0 .777-.635 1.407-1.418 1.407-.783 0-1.418-.63-1.418-1.407s.635-1.407 1.418-1.407c.783 0 1.418.63 1.418 1.407zm4.563 0c0 .777-.635 1.407-1.418 1.407-.783 0-1.418-.63-1.418-1.407s.635-1.407 1.418-1.407c.783 0 1.418.63 1.418 1.407zm4.565 0c0 .777-.635 1.407-1.418 1.407-.783 0-1.418-.63-1.418-1.407s.635-1.407 1.418-1.407c.783 0 1.418.63 1.418 1.407z": "Rocket.Chat" "M22.915 8.321c-.642-.997-1.542-1.879-2.672-2.624-2.185-1.436-5.056-2.227-8.084-2.227-1.012 0-2.009.088-2.976.262a9.84 9.84 0 0 0-2.046-1.509C4.378.848 1.947 1.361.719 1.802a.59.59 0 0 0-.229.964c.866.894 2.299 2.66 1.946 4.267C1.067 8.431.324 10.117.324 11.872c0 1.789.742 3.475 2.112 4.873.352 1.607-1.081 3.374-1.947 4.268a.589.589 0 0 0 .229.963c1.228.442 3.659.955 6.418-.421a9.892 9.892 0 0 0 2.046-1.509c.968.174 1.964.262 2.976.262 3.029 0 5.9-.79 8.084-2.226 1.131-.744 2.031-1.626 2.672-2.624.715-1.11 1.077-2.306 1.077-3.552.001-1.279-.361-2.473-1.076-3.585zm-10.881 9.916c-1.309 0-2.558-.169-3.696-.474l-.832.8A7.609 7.609 0 0 1 5.972 19.7a6.033 6.033 0 0 1-2.17.613c.041-.073.078-.147.117-.221.833-1.531 1.059-2.907.674-4.128-1.363-1.071-2.181-2.442-2.181-3.935 0-3.427 4.308-6.206 9.621-6.206 5.313 0 9.622 2.779 9.622 6.206.001 3.429-4.307 6.208-9.621 6.208zM8.85 12.01c0 .777-.635 1.407-1.418 1.407-.783 0-1.418-.63-1.418-1.407s.635-1.407 1.418-1.407c.783 0 1.418.63 1.418 1.407zm4.563 0c0 .777-.635 1.407-1.418 1.407-.783 0-1.418-.63-1.418-1.407s.635-1.407 1.418-1.407c.783 0 1.418.63 1.418 1.407zm4.565 0c0 .777-.635 1.407-1.418 1.407-.783 0-1.418-.63-1.418-1.407s.635-1.407 1.418-1.407c.783 0 1.418.63 1.418 1.407z": "Rocket.Chat"
},
"ineffective-segments": {
"M12 1.25l6.75 6.637V2L12 1.25zm0 0l-6.05 7h12.1l-6.05-7zm0 0L5.25 2v5.887L12 1.25zM5.25 2L0 9l4.416-.68L5.25 2zM0 9l11.959 13.703.008-.014L4.443 9H0zm18.75-7l.834 6.32L24 9l-5.25-7zM24 9h-4.506l-7.523 13.69.029.06L24 9zM12 22.75l-.031-.057-.008.012.039.045zM5.436 9l6.533 13.686L18.564 9H5.436Z": "Sketch"
} }
} }

View File

@ -28,6 +28,7 @@ const svglintIgnores = JSON.parse(fs.readFileSync(svglintIgnoredFile, 'utf8'));
const svgRegexp = const svgRegexp =
/^<svg( [^\s]*=".*"){3}><title>.*<\/title><path d=".*"\/><\/svg>$/; /^<svg( [^\s]*=".*"){3}><title>.*<\/title><path d=".*"\/><\/svg>$/;
const negativeZerosRegexp = /-0(?=[^\.]|[\s\d\w]|$)/g; const negativeZerosRegexp = /-0(?=[^\.]|[\s\d\w]|$)/g;
const svgPathRegexp = /^[Mm][MmZzLlHhVvCcSsQqTtAaEe0-9\-,. ]+$/;
const iconSize = 24; const iconSize = 24;
const iconFloatPrecision = 3; const iconFloatPrecision = 3;
@ -105,14 +106,17 @@ const getTitleTextIndex = (svgFileContent) => {
const hexadecimalToDecimal = (hex) => { const hexadecimalToDecimal = (hex) => {
let result = 0, let result = 0,
digitValue; digitValue;
hex = hex.toLowerCase(); for (const digit of hex.toLowerCase()) {
for (var i = 0; i < hex.length; i++) { digitValue = '0123456789abcdefgh'.indexOf(digit);
digitValue = '0123456789abcdefgh'.indexOf(hex[i]);
result = result * 16 + digitValue; result = result * 16 + digitValue;
} }
return result; return result;
}; };
const maybeShortenedWithEllipsis = (str) => {
return str.length > 20 ? `${str.substring(0, 20)}...` : str;
};
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
@ -154,7 +158,8 @@ export default {
}, },
attr: [ attr: [
{ {
// ensure that the SVG elm has the appropriate attrs alphabetically ordered // ensure that the SVG element has the appropriate attributes
// alphabetically ordered
role: 'img', role: 'img',
viewBox: `0 0 ${iconSize} ${iconSize}`, viewBox: `0 0 ${iconSize} ${iconSize}`,
xmlns: 'http://www.w3.org/2000/svg', xmlns: 'http://www.w3.org/2000/svg',
@ -163,13 +168,14 @@ export default {
'rule::order': true, 'rule::order': true,
}, },
{ {
// ensure that the title elm has the appropriate attr // ensure that the title element has the appropriate attribute
'rule::selector': 'svg > title', 'rule::selector': 'svg > title',
'rule::whitelist': true, 'rule::whitelist': true,
}, },
{ {
// ensure that the path element only has the 'd' attr (no style, opacity, etc.) // ensure that the path element only has the 'd' attribute
d: /^[,a-zA-Z0-9\. -]+$/, // (no style, opacity, etc.)
d: svgPathRegexp,
'rule::selector': 'svg > path', 'rule::selector': 'svg > path',
'rule::whitelist': true, 'rule::whitelist': true,
}, },
@ -190,7 +196,7 @@ export default {
if (hexadecimalCodepoints.length > 0) { if (hexadecimalCodepoints.length > 0) {
_validCodepointsRepr = false; _validCodepointsRepr = false;
hexadecimalCodepoints.forEach((match) => { for (const match of hexadecimalCodepoints) {
const charHexReprIndex = const charHexReprIndex =
getTitleTextIndex(ast.source) + match.index + 1; getTitleTextIndex(ast.source) + match.index + 1;
const charDec = hexadecimalToDecimal(match[1]); const charDec = hexadecimalToDecimal(match[1]);
@ -207,10 +213,11 @@ export default {
} }
reporter.error( reporter.error(
`Hexadecimal representation of encoded character "${match[0]}" found at index ${charHexReprIndex}:` + 'Hexadecimal representation of encoded character' +
` "${match[0]}" found at index ${charHexReprIndex}:` +
` replace it with "${charRepr}".`, ` replace it with "${charRepr}".`,
); );
}); }
} }
// avoid character codepoints as named entities // avoid character codepoints as named entities
@ -218,7 +225,7 @@ export default {
iconTitleText.matchAll(/&([A-Za-z0-9]+);/g), iconTitleText.matchAll(/&([A-Za-z0-9]+);/g),
); );
if (namedEntitiesCodepoints.length > 0) { if (namedEntitiesCodepoints.length > 0) {
namedEntitiesCodepoints.forEach((match) => { for (const match of namedEntitiesCodepoints) {
const namedEntiyReprIndex = const namedEntiyReprIndex =
getTitleTextIndex(ast.source) + match.index + 1; getTitleTextIndex(ast.source) + match.index + 1;
@ -242,11 +249,12 @@ export default {
} }
reporter.error( reporter.error(
`Named entity representation of encoded character "${match[0]}" found at index ${namedEntiyReprIndex}.` + 'Named entity representation of encoded character' +
` "${match[0]}" found at index ${namedEntiyReprIndex}.` +
` Replace it with ${replacement}.`, ` Replace it with ${replacement}.`,
); );
} }
}); }
} }
if (_validCodepointsRepr) { if (_validCodepointsRepr) {
@ -256,16 +264,15 @@ export default {
), ),
encodedBuf = []; encodedBuf = [];
const _indexesToIgnore = []; const indexesToIgnore = [];
for (let m = 0; m < encodingMatches.length; m++) { for (const match of encodingMatches) {
let index = encodingMatches[m].index; for (let r = match.index; r < match.index + match[0].length; r++) {
for (let r = index; r < index + encodingMatches[m][0].length; r++) { indexesToIgnore.push(r);
_indexesToIgnore.push(r);
} }
} }
for (let i = iconTitleText.length - 1; i >= 0; i--) { for (let i = iconTitleText.length - 1; i >= 0; i--) {
if (_indexesToIgnore.includes(i)) { if (indexesToIgnore.includes(i)) {
encodedBuf.unshift(iconTitleText[i]); encodedBuf.unshift(iconTitleText[i]);
} else { } else {
// encode all non ascii characters plus "'&<> (XML named entities) // encode all non ascii characters plus "'&<> (XML named entities)
@ -298,32 +305,32 @@ export default {
// check if there are some other encoded characters in decimal notation // check if there are some other encoded characters in decimal notation
// which shouldn't be encoded // which shouldn't be encoded
encodingMatches for (const match of encodingMatches.filter((m) => !isNaN(m[2]))) {
.filter((m) => !isNaN(m[2])) const decimalNumber = parseInt(match[2]);
.forEach((match) => { if (decimalNumber > 127) {
const decimalNumber = parseInt(match[2]); continue;
if (decimalNumber < 128) { }
_validCodepointsRepr = false; _validCodepointsRepr = false;
const decimalCodepointCharIndex = const decimalCodepointCharIndex =
getTitleTextIndex(ast.source) + match.index + 1; getTitleTextIndex(ast.source) + match.index + 1;
if (xmlNamedEntitiesCodepoints.includes(decimalNumber)) { if (xmlNamedEntitiesCodepoints.includes(decimalNumber)) {
replacement = `"&${ replacement = `"&${
xmlNamedEntities[ xmlNamedEntities[
xmlNamedEntitiesCodepoints.indexOf(decimalNumber) xmlNamedEntitiesCodepoints.indexOf(decimalNumber)
] ]
};"`; };"`;
} else { } else {
replacement = String.fromCharCode(decimalNumber); replacement = String.fromCodePoint(decimalNumber);
replacement = replacement == '"' ? `'"'` : `"${replacement}"`; replacement = replacement == '"' ? `'"'` : `"${replacement}"`;
} }
reporter.error( reporter.error(
`Unnecessary encoded character "${match[0]}" found at index ${decimalCodepointCharIndex}:` + `Unnecessary encoded character "${match[0]}" found` +
` replace it with ${replacement}.`, ` at index ${decimalCodepointCharIndex}:` +
); ` replace it with ${replacement}.`,
} );
}); }
if (_validCodepointsRepr) { if (_validCodepointsRepr) {
const iconName = htmlFriendlyToTitle(iconTitleText); const iconName = htmlFriendlyToTitle(iconTitleText);
@ -338,7 +345,7 @@ export default {
} }
} }
}, },
(reporter, $, ast) => { (reporter, $) => {
reporter.name = 'icon-size'; reporter.name = 'icon-size';
const iconPath = $.find('path').attr('d'); const iconPath = $.find('path').attr('d');
@ -359,7 +366,8 @@ export default {
} }
} else if (width !== iconSize && height !== iconSize) { } else if (width !== iconSize && height !== iconSize) {
reporter.error( reporter.error(
`Size of <path> must be exactly ${iconSize} in one dimension; the size is currently ${width} x ${height}`, `Size of <path> must be exactly ${iconSize} in one dimension;` +
` the size is currently ${width} x ${height}`,
); );
if (updateIgnoreFile) { if (updateIgnoreFile) {
ignoreIcon(reporter.name, iconPath, $); ignoreIcon(reporter.name, iconPath, $);
@ -370,51 +378,36 @@ export default {
reporter.name = 'icon-precision'; reporter.name = 'icon-precision';
const iconPath = $.find('path').attr('d'); const iconPath = $.find('path').attr('d');
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) { const segments = parsePath(iconPath);
return;
}
const segments = parsePath(iconPath), for (const segment of segments) {
svgFileContent = $.html();
segments.forEach((segment) => {
const precisionMax = Math.max( const precisionMax = Math.max(
...segment.params.slice(1).map(countDecimals), ...segment.params.slice(1).map(countDecimals),
); );
if (precisionMax > iconMaxFloatPrecision) { if (precisionMax > iconMaxFloatPrecision) {
let errorMsg = `found ${precisionMax} decimals in segment "${iconPath.substring( let errorMsg =
segment.start, `found ${precisionMax} decimals in segment` +
segment.end, ` "${iconPath.substring(segment.start, segment.end)}"`;
)}"`;
if (segment.chained) { if (segment.chained) {
let readableChain = iconPath.substring( const readableChain = maybeShortenedWithEllipsis(
segment.chainStart, iconPath.substring(segment.chainStart, segment.chainEnd),
segment.chainEnd,
); );
if (readableChain.length > 20) {
readableChain = `${readableChain.substring(0, 20)}...`;
}
errorMsg += ` of chain "${readableChain}"`; errorMsg += ` of chain "${readableChain}"`;
} }
errorMsg += ` at index ${ errorMsg += ` at index ${
segment.start + getPathDIndex(svgFileContent) segment.start + getPathDIndex(ast.source)
}`; }`;
reporter.error( reporter.error(
`Maximum precision should not be greater than ${iconMaxFloatPrecision}; ${errorMsg}`, 'Maximum precision should not be greater than' +
` ${iconMaxFloatPrecision}; ${errorMsg}`,
); );
if (updateIgnoreFile) {
ignoreIcon(reporter.name, iconPath, $);
}
} }
}); }
}, },
(reporter, $, ast) => { (reporter, $, ast) => {
reporter.name = 'ineffective-segments'; reporter.name = 'ineffective-segments';
const iconPath = $.find('path').attr('d'); const iconPath = $.find('path').attr('d');
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) {
return;
}
const segments = parsePath(iconPath); const segments = parsePath(iconPath);
const absSegments = svgpath(iconPath).abs().unshort().segments; const absSegments = svgpath(iconPath).abs().unshort().segments;
@ -451,6 +444,7 @@ export default {
const isInvalidSegment = ( const isInvalidSegment = (
[command, x1Coord, y1Coord, ...rest], [command, x1Coord, y1Coord, ...rest],
index, index,
previousSegmentIsZ,
) => { ) => {
if (commands.includes(command)) { if (commands.includes(command)) {
// Relative directions (h or v) having a length of 0 // Relative directions (h or v) having a length of 0
@ -464,7 +458,9 @@ export default {
x1Coord === 0 && x1Coord === 0 &&
y1Coord === 0 y1Coord === 0
) { ) {
return true; // When the path is closed (z), the new segment can start with
// a relative placement (m) as if it were absolute (M)
return command.toLowerCase() === 'm' ? !previousSegmentIsZ : true;
} }
if ( if (
lowerCurveCommands.includes(command) && lowerCurveCommands.includes(command) &&
@ -487,7 +483,8 @@ export default {
let [yPrevCoord, xPrevCoord] = [ let [yPrevCoord, xPrevCoord] = [
...absSegments[index - 1], ...absSegments[index - 1],
].reverse(); ].reverse();
// If the previous command was a direction one, we need to iterate back until we find the missing coordinates // If the previous command was a direction one,
// we need to iterate back until we find the missing coordinates
if (upperDirectionCommands.includes(xPrevCoord)) { if (upperDirectionCommands.includes(xPrevCoord)) {
xPrevCoord = undefined; xPrevCoord = undefined;
yPrevCoord = undefined; yPrevCoord = undefined;
@ -499,12 +496,14 @@ export default {
let [yPrevCoordDeep, xPrevCoordDeep] = [ let [yPrevCoordDeep, xPrevCoordDeep] = [
...absSegments[idx], ...absSegments[idx],
].reverse(); ].reverse();
// If the previous command was a horizontal movement, we need to consider the single coordinate as x // If the previous command was a horizontal movement,
// we need to consider the single coordinate as x
if (upperHorDirectionCommand === xPrevCoordDeep) { if (upperHorDirectionCommand === xPrevCoordDeep) {
xPrevCoordDeep = yPrevCoordDeep; xPrevCoordDeep = yPrevCoordDeep;
yPrevCoordDeep = undefined; yPrevCoordDeep = undefined;
} }
// If the previous command was a vertical movement, we need to consider the single coordinate as y // If the previous command was a vertical movement,
// we need to consider the single coordinate as y
if (upperVerDirectionCommand === xPrevCoordDeep) { if (upperVerDirectionCommand === xPrevCoordDeep) {
xPrevCoordDeep = undefined; xPrevCoordDeep = undefined;
} }
@ -525,7 +524,9 @@ export default {
if (upperCurveCommands.includes(command)) { if (upperCurveCommands.includes(command)) {
const [x2Coord, y2Coord, xCoord, yCoord] = rest; const [x2Coord, y2Coord, xCoord, yCoord] = rest;
// Absolute shorthand curve (S) having the same coordinate as the previous segment and a control point equal to the ending point // Absolute shorthand curve (S) having
// the same coordinate as the previous segment
// and a control point equal to the ending point
if ( if (
upperShorthandCurveCommand === command && upperShorthandCurveCommand === command &&
x1Coord === xPrevCoord && x1Coord === xPrevCoord &&
@ -535,7 +536,9 @@ export default {
) { ) {
return true; return true;
} }
// Absolute bézier curve (C) having the same coordinate as the previous segment and last control point equal to the ending point // Absolute bézier curve (C) having
// the same coordinate as the previous segment
// and last control point equal to the ending point
if ( if (
upperCurveCommand === command && upperCurveCommand === command &&
x1Coord === xPrevCoord && x1Coord === xPrevCoord &&
@ -548,13 +551,16 @@ export default {
} }
return ( return (
// Absolute horizontal direction (H) having the same x coordinate as the previous segment // Absolute horizontal direction (H) having
// the same x coordinate as the previous segment
(upperHorDirectionCommand === command && (upperHorDirectionCommand === command &&
x1Coord === xPrevCoord) || x1Coord === xPrevCoord) ||
// Absolute vertical direction (V) having the same y coordinate as the previous segment // Absolute vertical direction (V) having
// the same y coordinate as the previous segment
(upperVerDirectionCommand === command && (upperVerDirectionCommand === command &&
x1Coord === yPrevCoord) || x1Coord === yPrevCoord) ||
// Absolute movement (M or L) having the same coordinate as the previous segment // Absolute movement (M or L) having the same
// coordinate as the previous segment
(upperMovementCommands.includes(command) && (upperMovementCommands.includes(command) &&
x1Coord === xPrevCoord && x1Coord === xPrevCoord &&
y1Coord === yPrevCoord) y1Coord === yPrevCoord)
@ -563,10 +569,12 @@ export default {
} }
}; };
const svgFileContent = $.html(); for (let index = 0; index < segments.length; index++) {
const segment = segments[index];
const previousSegmentIsZ =
index > 0 && segments[index - 1].params[0].toLowerCase() === 'z';
segments.forEach((segment, index) => { if (isInvalidSegment(segment.params, index, previousSegmentIsZ)) {
if (isInvalidSegment(segment.params, index)) {
const [command, x1, y1, ...rest] = segment.params; const [command, x1, y1, ...rest] = segment.params;
let errorMsg = `Innefective segment "${iconPath.substring( let errorMsg = `Innefective segment "${iconPath.substring(
@ -604,34 +612,23 @@ export default {
} }
if (segment.chained) { if (segment.chained) {
let readableChain = iconPath.substring( const readableChain = maybeShortenedWithEllipsis(
segment.chainStart, iconPath.substring(segment.chainStart, segment.chainEnd),
segment.chainEnd,
); );
if (readableChain.length > 20) {
readableChain = `${readableChain.substring(0, 20)}...`;
}
errorMsg += ` in chain "${readableChain}"`; errorMsg += ` in chain "${readableChain}"`;
} }
errorMsg += ` at index ${ errorMsg += ` at index ${
segment.start + getPathDIndex(svgFileContent) segment.start + getPathDIndex(ast.source)
}`; }`;
reporter.error(`${errorMsg} (${resolutionTip})`); reporter.error(`${errorMsg} (${resolutionTip})`);
if (updateIgnoreFile) {
ignoreIcon(reporter.name, iconPath, $);
}
} }
}); }
}, },
(reporter, $, ast) => { (reporter, $, ast) => {
reporter.name = 'collinear-segments'; reporter.name = 'collinear-segments';
const iconPath = $.find('path').attr('d'); const iconPath = $.find('path').attr('d');
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) {
return;
}
/** /**
* Extracts collinear coordinates from SVG path straight lines * Extracts collinear coordinates from SVG path straight lines
@ -640,8 +637,7 @@ export default {
const getCollinearSegments = (iconPath) => { const getCollinearSegments = (iconPath) => {
const segments = parsePath(iconPath), const segments = parsePath(iconPath),
collinearSegments = [], collinearSegments = [],
straightLineCommands = 'HhVvLlMm', straightLineCommands = 'HhVvLlMm';
zCommands = 'Zz';
let currLine = [], let currLine = [],
currAbsCoord = [undefined, undefined], currAbsCoord = [undefined, undefined],
@ -655,80 +651,103 @@ export default {
cmd = seg[0], cmd = seg[0],
nextCmd = s + 1 < segments.length ? segments[s + 1][0] : null; nextCmd = s + 1 < segments.length ? segments[s + 1][0] : null;
if (cmd === 'L') { switch (cmd) {
currAbsCoord[0] = seg[1]; // Next switch cases have been ordered by frequency
currAbsCoord[1] = seg[2]; // of occurrence in the SVG paths of the icons
} else if (cmd === 'l') { case 'M':
currAbsCoord[0] = currAbsCoord[0] = seg[1];
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1]; currAbsCoord[1] = seg[2];
currAbsCoord[1] = startPoint = undefined;
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2]; break;
} else if (cmd === 'm') { case 'm':
currAbsCoord[0] = currAbsCoord[0] =
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1]; (!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
currAbsCoord[1] = currAbsCoord[1] =
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2]; (!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
startPoint = undefined; startPoint = undefined;
} else if (cmd === 'M') { break;
currAbsCoord[0] = seg[1]; case 'H':
currAbsCoord[1] = seg[2]; currAbsCoord[0] = seg[1];
startPoint = undefined; break;
} else if (cmd === 'H') { case 'h':
currAbsCoord[0] = seg[1]; currAbsCoord[0] =
} else if (cmd === 'h') { (!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
currAbsCoord[0] = break;
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1]; case 'V':
} else if (cmd === 'V') { currAbsCoord[1] = seg[1];
currAbsCoord[1] = seg[1]; break;
} else if (cmd === 'v') { case 'v':
currAbsCoord[1] = currAbsCoord[1] =
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[1]; (!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[1];
} else if (cmd === 'C') { break;
currAbsCoord[0] = seg[5]; case 'L':
currAbsCoord[1] = seg[6]; currAbsCoord[0] = seg[1];
} else if (cmd === 'a') { currAbsCoord[1] = seg[2];
currAbsCoord[0] = break;
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[6]; case 'l':
currAbsCoord[1] = currAbsCoord[0] =
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[7]; (!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
} else if (cmd === 'A') { currAbsCoord[1] =
currAbsCoord[0] = seg[6]; (!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
currAbsCoord[1] = seg[7]; break;
} else if (cmd === 's') { case 'Z':
currAbsCoord[0] = case 'z':
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1]; // Overlapping in Z should be handled in another rule
currAbsCoord[1] = currAbsCoord = [startPoint[0], startPoint[1]];
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2]; _resetStartPoint = true;
} else if (cmd === 'S') { break;
currAbsCoord[0] = seg[1]; case 'C':
currAbsCoord[1] = seg[2]; currAbsCoord[0] = seg[5];
} else if (cmd === 't') { currAbsCoord[1] = seg[6];
currAbsCoord[0] = break;
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1]; case 'c':
currAbsCoord[1] = currAbsCoord[0] =
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2]; (!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[5];
} else if (cmd === 'T') { currAbsCoord[1] =
currAbsCoord[0] = seg[1]; (!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[6];
currAbsCoord[1] = seg[2]; break;
} else if (cmd === 'c') { case 'A':
currAbsCoord[0] = currAbsCoord[0] = seg[6];
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[5]; currAbsCoord[1] = seg[7];
currAbsCoord[1] = break;
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[6]; case 'a':
} else if (cmd === 'Q') { currAbsCoord[0] =
currAbsCoord[0] = seg[3]; (!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[6];
currAbsCoord[1] = seg[4]; currAbsCoord[1] =
} else if (cmd === 'q') { (!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[7];
currAbsCoord[0] = break;
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[3]; case 's':
currAbsCoord[1] = currAbsCoord[0] =
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[4]; (!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
} else if (zCommands.includes(cmd)) { currAbsCoord[1] =
// Overlapping in Z should be handled in another rule (!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
currAbsCoord = [startPoint[0], startPoint[1]]; break;
_resetStartPoint = true; case 'S':
} else { currAbsCoord[0] = seg[1];
throw new Error(`"${cmd}" command not handled`); currAbsCoord[1] = seg[2];
break;
case 't':
currAbsCoord[0] =
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
currAbsCoord[1] =
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
break;
case 'T':
currAbsCoord[0] = seg[1];
currAbsCoord[1] = seg[2];
break;
case 'Q':
currAbsCoord[0] = seg[3];
currAbsCoord[1] = seg[4];
break;
case 'q':
currAbsCoord[0] =
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[3];
currAbsCoord[1] =
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[4];
break;
default:
throw new Error(`"${cmd}" command not handled`);
} }
if (startPoint === undefined) { if (startPoint === undefined) {
@ -774,32 +793,22 @@ export default {
}; };
const collinearSegments = getCollinearSegments(iconPath), const collinearSegments = getCollinearSegments(iconPath),
pathDIndex = getPathDIndex($.html()); pathDIndex = getPathDIndex(ast.source);
collinearSegments.forEach((segment) => { for (const segment of collinearSegments) {
let errorMsg = `Collinear segment "${iconPath.substring( let errorMsg = `Collinear segment "${iconPath.substring(
segment.start, segment.start,
segment.end, segment.end,
)}" found`; )}" found`;
if (segment.chained) { if (segment.chained) {
let readableChain = iconPath.substring( let readableChain = maybeShortenedWithEllipsis(
segment.chainStart, iconPath.substring(segment.chainStart, segment.chainEnd),
segment.chainEnd,
); );
if (readableChain.length > 20) {
readableChain = `${readableChain.substring(0, 20)}...`;
}
errorMsg += ` in chain "${readableChain}"`; errorMsg += ` in chain "${readableChain}"`;
} }
errorMsg += ` at index ${ errorMsg += ` at index ${
segment.start + pathDIndex segment.start + pathDIndex
} (should be removed)`; } (should be removed)`;
reporter.error(errorMsg); reporter.error(errorMsg);
});
if (collinearSegments.length) {
if (updateIgnoreFile) {
ignoreIcon(reporter.name, iconPath, $);
}
} }
}, },
(reporter, $, ast) => { (reporter, $, ast) => {
@ -812,7 +821,8 @@ export default {
); );
} else { } else {
reporter.error( reporter.error(
'Unexpected character(s), most likely extraneous whitespace, detected in SVG markup', 'Unexpected character(s), most likely extraneous' +
' whitespace, detected in SVG markup',
); );
} }
} }
@ -821,9 +831,6 @@ export default {
reporter.name = 'negative-zeros'; reporter.name = 'negative-zeros';
const iconPath = $.find('path').attr('d'); const iconPath = $.find('path').attr('d');
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) {
return;
}
// Find negative zeros inside path // Find negative zeros inside path
const negativeZeroMatches = Array.from( const negativeZeroMatches = Array.from(
@ -831,22 +838,22 @@ export default {
); );
if (negativeZeroMatches.length) { if (negativeZeroMatches.length) {
// Calculate the index for each match in the file // Calculate the index for each match in the file
const svgFileContent = $.html(); const pathDIndex = getPathDIndex(ast.source);
const pathDIndex = getPathDIndex(svgFileContent);
negativeZeroMatches.forEach((match) => { for (const match of negativeZeroMatches) {
const negativeZeroFileIndex = match.index + pathDIndex; const negativeZeroFileIndex = match.index + pathDIndex;
const previousChar = svgFileContent[negativeZeroFileIndex - 1]; const previousChar = ast.source[negativeZeroFileIndex - 1];
const replacement = '0123456789'.includes(previousChar) const replacement = '0123456789'.includes(previousChar)
? ' 0' ? ' 0'
: '0'; : '0';
reporter.error( reporter.error(
`Found "-0" at index ${negativeZeroFileIndex} (should be "${replacement}")`, `Found "-0" at index ${negativeZeroFileIndex} (should` +
` be "${replacement}")`,
); );
}); }
} }
}, },
(reporter, $, ast) => { (reporter, $) => {
reporter.name = 'icon-centered'; reporter.name = 'icon-centered';
const iconPath = $.find('path').attr('d'); const iconPath = $.find('path').attr('d');
@ -866,7 +873,8 @@ export default {
Math.abs(devianceY) > iconTolerance Math.abs(devianceY) > iconTolerance
) { ) {
reporter.error( reporter.error(
`<path> must be centered at (${targetCenter}, ${targetCenter}); the center is currently (${centerX}, ${centerY})`, `<path> must be centered at (${targetCenter}, ${targetCenter});` +
` the center is currently (${centerX}, ${centerY})`,
); );
if (updateIgnoreFile) { if (updateIgnoreFile) {
ignoreIcon(reporter.name, iconPath, $); ignoreIcon(reporter.name, iconPath, $);
@ -878,20 +886,24 @@ export default {
const iconPath = $.find('path').attr('d'); const iconPath = $.find('path').attr('d');
const validPathFormatRegex = /^[Mm][MmZzLlHhVvCcSsQqTtAaEe0-9-,.\s]+$/; if (!svgPathRegexp.test(iconPath)) {
if (!validPathFormatRegex.test(iconPath)) {
let errorMsg = 'Invalid path format', let errorMsg = 'Invalid path format',
reason; reason;
if (!/^[Mm]/.test(iconPath)) { if (!/^[Mm]/.test(iconPath)) {
// doesn't start with moveto // doesn't start with moveto
reason = `should start with \"moveto\" command (\"M\" or \"m\"), but starts with \"${iconPath[0]}\"`; reason =
'should start with "moveto" command ("M" or "m"),' +
` but starts with \"${iconPath[0]}\"`;
reporter.error(`${errorMsg}: ${reason}`); reporter.error(`${errorMsg}: ${reason}`);
} }
const validPathCharacters = 'MmZzLlHhVvCcSsQqTtAaEe0123456789-,. ', const validPathCharacters = svgPathRegexp.source.replace(
/[\[\]+^$]/g,
'',
),
invalidCharactersMsgs = [], invalidCharactersMsgs = [],
pathDIndex = getPathDIndex($.html()); pathDIndex = getPathDIndex(ast.source);
for (let [i, char] of Object.entries(iconPath)) { for (let [i, char] of Object.entries(iconPath)) {
if (validPathCharacters.indexOf(char) === -1) { if (validPathCharacters.indexOf(char) === -1) {
@ -919,8 +931,8 @@ export default {
const reason = const reason =
`found a closing "path" tag at index ${ast.source.indexOf( `found a closing "path" tag at index ${ast.source.indexOf(
'</path>', '</path>',
)}.` + )}. The path should be self-closing,` +
" The path should be self-closing, use '/>' instead of '></path>'."; ' use "/>" instead of "></path>".';
reporter.error(`Invalid SVG content format: ${reason}`); reporter.error(`Invalid SVG content format: ${reason}`);
} }
}, },

View File

@ -34,6 +34,7 @@ const generateSdkMts = async () => {
' --declaration --emitDeclarationOnly --allowJs --removeComments', ' --declaration --emitDeclarationOnly --allowJs --removeComments',
); );
} catch (error) { } catch (error) {
await fs.writeFile(sdkMjs, originalSdkMjsContent);
console.log( console.log(
`Error ${error.status} generating Typescript` + `Error ${error.status} generating Typescript` +
` definitions: '${error.message}'`, ` definitions: '${error.message}'`,

View File

@ -112,7 +112,7 @@ export const titleToHtmlFriendly = (brandTitle) =>
*/ */
export const htmlFriendlyToTitle = (htmlFriendlyTitle) => export const htmlFriendlyToTitle = (htmlFriendlyTitle) =>
htmlFriendlyTitle htmlFriendlyTitle
.replace(/&#([0-9]+);/g, (_, num) => String.fromCharCode(parseInt(num))) .replace(/&#([0-9]+);/g, (_, num) => String.fromCodePoint(parseInt(num)))
.replace( .replace(
/&(quot|amp|lt|gt);/g, /&(quot|amp|lt|gt);/g,
(_, ref) => ({ quot: '"', amp: '&', lt: '<', gt: '>' }[ref]), (_, ref) => ({ quot: '"', amp: '&', lt: '<', gt: '>' }[ref]),