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:
parent
fe7d231142
commit
720a0e4d53
@ -4,8 +4,5 @@
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
336
.svglintrc.mjs
336
.svglintrc.mjs
@ -28,6 +28,7 @@ const svglintIgnores = JSON.parse(fs.readFileSync(svglintIgnoredFile, 'utf8'));
|
||||
const svgRegexp =
|
||||
/^<svg( [^\s]*=".*"){3}><title>.*<\/title><path d=".*"\/><\/svg>$/;
|
||||
const negativeZerosRegexp = /-0(?=[^\.]|[\s\d\w]|$)/g;
|
||||
const svgPathRegexp = /^[Mm][MmZzLlHhVvCcSsQqTtAaEe0-9\-,. ]+$/;
|
||||
|
||||
const iconSize = 24;
|
||||
const iconFloatPrecision = 3;
|
||||
@ -105,14 +106,17 @@ const getTitleTextIndex = (svgFileContent) => {
|
||||
const hexadecimalToDecimal = (hex) => {
|
||||
let result = 0,
|
||||
digitValue;
|
||||
hex = hex.toLowerCase();
|
||||
for (var i = 0; i < hex.length; i++) {
|
||||
digitValue = '0123456789abcdefgh'.indexOf(hex[i]);
|
||||
for (const digit of hex.toLowerCase()) {
|
||||
digitValue = '0123456789abcdefgh'.indexOf(digit);
|
||||
result = result * 16 + digitValue;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const maybeShortenedWithEllipsis = (str) => {
|
||||
return str.length > 20 ? `${str.substring(0, 20)}...` : str;
|
||||
};
|
||||
|
||||
if (updateIgnoreFile) {
|
||||
process.on('exit', () => {
|
||||
// ensure object output order is consistent due to async svglint processing
|
||||
@ -154,7 +158,8 @@ export default {
|
||||
},
|
||||
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',
|
||||
viewBox: `0 0 ${iconSize} ${iconSize}`,
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
@ -163,13 +168,14 @@ export default {
|
||||
'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::whitelist': true,
|
||||
},
|
||||
{
|
||||
// ensure that the path element only has the 'd' attr (no style, opacity, etc.)
|
||||
d: /^[,a-zA-Z0-9\. -]+$/,
|
||||
// ensure that the path element only has the 'd' attribute
|
||||
// (no style, opacity, etc.)
|
||||
d: svgPathRegexp,
|
||||
'rule::selector': 'svg > path',
|
||||
'rule::whitelist': true,
|
||||
},
|
||||
@ -190,7 +196,7 @@ export default {
|
||||
if (hexadecimalCodepoints.length > 0) {
|
||||
_validCodepointsRepr = false;
|
||||
|
||||
hexadecimalCodepoints.forEach((match) => {
|
||||
for (const match of hexadecimalCodepoints) {
|
||||
const charHexReprIndex =
|
||||
getTitleTextIndex(ast.source) + match.index + 1;
|
||||
const charDec = hexadecimalToDecimal(match[1]);
|
||||
@ -207,10 +213,11 @@ export default {
|
||||
}
|
||||
|
||||
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}".`,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// avoid character codepoints as named entities
|
||||
@ -218,7 +225,7 @@ export default {
|
||||
iconTitleText.matchAll(/&([A-Za-z0-9]+);/g),
|
||||
);
|
||||
if (namedEntitiesCodepoints.length > 0) {
|
||||
namedEntitiesCodepoints.forEach((match) => {
|
||||
for (const match of namedEntitiesCodepoints) {
|
||||
const namedEntiyReprIndex =
|
||||
getTitleTextIndex(ast.source) + match.index + 1;
|
||||
|
||||
@ -242,11 +249,12 @@ export default {
|
||||
}
|
||||
|
||||
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}.`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (_validCodepointsRepr) {
|
||||
@ -256,16 +264,15 @@ export default {
|
||||
),
|
||||
encodedBuf = [];
|
||||
|
||||
const _indexesToIgnore = [];
|
||||
for (let m = 0; m < encodingMatches.length; m++) {
|
||||
let index = encodingMatches[m].index;
|
||||
for (let r = index; r < index + encodingMatches[m][0].length; r++) {
|
||||
_indexesToIgnore.push(r);
|
||||
const indexesToIgnore = [];
|
||||
for (const match of encodingMatches) {
|
||||
for (let r = match.index; r < match.index + match[0].length; r++) {
|
||||
indexesToIgnore.push(r);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = iconTitleText.length - 1; i >= 0; i--) {
|
||||
if (_indexesToIgnore.includes(i)) {
|
||||
if (indexesToIgnore.includes(i)) {
|
||||
encodedBuf.unshift(iconTitleText[i]);
|
||||
} else {
|
||||
// encode all non ascii characters plus "'&<> (XML named entities)
|
||||
@ -298,11 +305,11 @@ export default {
|
||||
|
||||
// check if there are some other encoded characters in decimal notation
|
||||
// which shouldn't be encoded
|
||||
encodingMatches
|
||||
.filter((m) => !isNaN(m[2]))
|
||||
.forEach((match) => {
|
||||
for (const match of encodingMatches.filter((m) => !isNaN(m[2]))) {
|
||||
const decimalNumber = parseInt(match[2]);
|
||||
if (decimalNumber < 128) {
|
||||
if (decimalNumber > 127) {
|
||||
continue;
|
||||
}
|
||||
_validCodepointsRepr = false;
|
||||
|
||||
const decimalCodepointCharIndex =
|
||||
@ -314,16 +321,16 @@ export default {
|
||||
]
|
||||
};"`;
|
||||
} else {
|
||||
replacement = String.fromCharCode(decimalNumber);
|
||||
replacement = String.fromCodePoint(decimalNumber);
|
||||
replacement = replacement == '"' ? `'"'` : `"${replacement}"`;
|
||||
}
|
||||
|
||||
reporter.error(
|
||||
`Unnecessary encoded character "${match[0]}" found at index ${decimalCodepointCharIndex}:` +
|
||||
`Unnecessary encoded character "${match[0]}" found` +
|
||||
` at index ${decimalCodepointCharIndex}:` +
|
||||
` replace it with ${replacement}.`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (_validCodepointsRepr) {
|
||||
const iconName = htmlFriendlyToTitle(iconTitleText);
|
||||
@ -338,7 +345,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
(reporter, $, ast) => {
|
||||
(reporter, $) => {
|
||||
reporter.name = 'icon-size';
|
||||
|
||||
const iconPath = $.find('path').attr('d');
|
||||
@ -359,7 +366,8 @@ export default {
|
||||
}
|
||||
} else if (width !== iconSize && height !== iconSize) {
|
||||
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) {
|
||||
ignoreIcon(reporter.name, iconPath, $);
|
||||
@ -370,51 +378,36 @@ export default {
|
||||
reporter.name = 'icon-precision';
|
||||
|
||||
const iconPath = $.find('path').attr('d');
|
||||
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) {
|
||||
return;
|
||||
}
|
||||
const segments = parsePath(iconPath);
|
||||
|
||||
const segments = parsePath(iconPath),
|
||||
svgFileContent = $.html();
|
||||
|
||||
segments.forEach((segment) => {
|
||||
for (const segment of segments) {
|
||||
const precisionMax = Math.max(
|
||||
...segment.params.slice(1).map(countDecimals),
|
||||
);
|
||||
if (precisionMax > iconMaxFloatPrecision) {
|
||||
let errorMsg = `found ${precisionMax} decimals in segment "${iconPath.substring(
|
||||
segment.start,
|
||||
segment.end,
|
||||
)}"`;
|
||||
let errorMsg =
|
||||
`found ${precisionMax} decimals in segment` +
|
||||
` "${iconPath.substring(segment.start, segment.end)}"`;
|
||||
if (segment.chained) {
|
||||
let readableChain = iconPath.substring(
|
||||
segment.chainStart,
|
||||
segment.chainEnd,
|
||||
const readableChain = maybeShortenedWithEllipsis(
|
||||
iconPath.substring(segment.chainStart, segment.chainEnd),
|
||||
);
|
||||
if (readableChain.length > 20) {
|
||||
readableChain = `${readableChain.substring(0, 20)}...`;
|
||||
}
|
||||
errorMsg += ` of chain "${readableChain}"`;
|
||||
}
|
||||
errorMsg += ` at index ${
|
||||
segment.start + getPathDIndex(svgFileContent)
|
||||
segment.start + getPathDIndex(ast.source)
|
||||
}`;
|
||||
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.name = 'ineffective-segments';
|
||||
|
||||
const iconPath = $.find('path').attr('d');
|
||||
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const segments = parsePath(iconPath);
|
||||
const absSegments = svgpath(iconPath).abs().unshort().segments;
|
||||
@ -451,6 +444,7 @@ export default {
|
||||
const isInvalidSegment = (
|
||||
[command, x1Coord, y1Coord, ...rest],
|
||||
index,
|
||||
previousSegmentIsZ,
|
||||
) => {
|
||||
if (commands.includes(command)) {
|
||||
// Relative directions (h or v) having a length of 0
|
||||
@ -464,7 +458,9 @@ export default {
|
||||
x1Coord === 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 (
|
||||
lowerCurveCommands.includes(command) &&
|
||||
@ -487,7 +483,8 @@ export default {
|
||||
let [yPrevCoord, xPrevCoord] = [
|
||||
...absSegments[index - 1],
|
||||
].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)) {
|
||||
xPrevCoord = undefined;
|
||||
yPrevCoord = undefined;
|
||||
@ -499,12 +496,14 @@ export default {
|
||||
let [yPrevCoordDeep, xPrevCoordDeep] = [
|
||||
...absSegments[idx],
|
||||
].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) {
|
||||
xPrevCoordDeep = yPrevCoordDeep;
|
||||
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) {
|
||||
xPrevCoordDeep = undefined;
|
||||
}
|
||||
@ -525,7 +524,9 @@ export default {
|
||||
|
||||
if (upperCurveCommands.includes(command)) {
|
||||
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 (
|
||||
upperShorthandCurveCommand === command &&
|
||||
x1Coord === xPrevCoord &&
|
||||
@ -535,7 +536,9 @@ export default {
|
||||
) {
|
||||
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 (
|
||||
upperCurveCommand === command &&
|
||||
x1Coord === xPrevCoord &&
|
||||
@ -548,13 +551,16 @@ export default {
|
||||
}
|
||||
|
||||
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 &&
|
||||
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 &&
|
||||
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) &&
|
||||
x1Coord === xPrevCoord &&
|
||||
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)) {
|
||||
if (isInvalidSegment(segment.params, index, previousSegmentIsZ)) {
|
||||
const [command, x1, y1, ...rest] = segment.params;
|
||||
|
||||
let errorMsg = `Innefective segment "${iconPath.substring(
|
||||
@ -604,34 +612,23 @@ export default {
|
||||
}
|
||||
|
||||
if (segment.chained) {
|
||||
let readableChain = iconPath.substring(
|
||||
segment.chainStart,
|
||||
segment.chainEnd,
|
||||
const readableChain = maybeShortenedWithEllipsis(
|
||||
iconPath.substring(segment.chainStart, segment.chainEnd),
|
||||
);
|
||||
if (readableChain.length > 20) {
|
||||
readableChain = `${readableChain.substring(0, 20)}...`;
|
||||
}
|
||||
errorMsg += ` in chain "${readableChain}"`;
|
||||
}
|
||||
errorMsg += ` at index ${
|
||||
segment.start + getPathDIndex(svgFileContent)
|
||||
segment.start + getPathDIndex(ast.source)
|
||||
}`;
|
||||
|
||||
reporter.error(`${errorMsg} (${resolutionTip})`);
|
||||
|
||||
if (updateIgnoreFile) {
|
||||
ignoreIcon(reporter.name, iconPath, $);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
(reporter, $, ast) => {
|
||||
reporter.name = 'collinear-segments';
|
||||
|
||||
const iconPath = $.find('path').attr('d');
|
||||
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts collinear coordinates from SVG path straight lines
|
||||
@ -640,8 +637,7 @@ export default {
|
||||
const getCollinearSegments = (iconPath) => {
|
||||
const segments = parsePath(iconPath),
|
||||
collinearSegments = [],
|
||||
straightLineCommands = 'HhVvLlMm',
|
||||
zCommands = 'Zz';
|
||||
straightLineCommands = 'HhVvLlMm';
|
||||
|
||||
let currLine = [],
|
||||
currAbsCoord = [undefined, undefined],
|
||||
@ -655,79 +651,102 @@ export default {
|
||||
cmd = seg[0],
|
||||
nextCmd = s + 1 < segments.length ? segments[s + 1][0] : null;
|
||||
|
||||
if (cmd === 'L') {
|
||||
switch (cmd) {
|
||||
// Next switch cases have been ordered by frequency
|
||||
// of occurrence in the SVG paths of the icons
|
||||
case 'M':
|
||||
currAbsCoord[0] = seg[1];
|
||||
currAbsCoord[1] = seg[2];
|
||||
} else if (cmd === 'l') {
|
||||
currAbsCoord[0] =
|
||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
|
||||
currAbsCoord[1] =
|
||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
|
||||
} else if (cmd === 'm') {
|
||||
startPoint = undefined;
|
||||
break;
|
||||
case 'm':
|
||||
currAbsCoord[0] =
|
||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
|
||||
currAbsCoord[1] =
|
||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
|
||||
startPoint = undefined;
|
||||
} else if (cmd === 'M') {
|
||||
break;
|
||||
case 'H':
|
||||
currAbsCoord[0] = seg[1];
|
||||
currAbsCoord[1] = seg[2];
|
||||
startPoint = undefined;
|
||||
} else if (cmd === 'H') {
|
||||
currAbsCoord[0] = seg[1];
|
||||
} else if (cmd === 'h') {
|
||||
break;
|
||||
case 'h':
|
||||
currAbsCoord[0] =
|
||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
|
||||
} else if (cmd === 'V') {
|
||||
break;
|
||||
case 'V':
|
||||
currAbsCoord[1] = seg[1];
|
||||
} else if (cmd === 'v') {
|
||||
break;
|
||||
case 'v':
|
||||
currAbsCoord[1] =
|
||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[1];
|
||||
} else if (cmd === 'C') {
|
||||
break;
|
||||
case 'L':
|
||||
currAbsCoord[0] = seg[1];
|
||||
currAbsCoord[1] = seg[2];
|
||||
break;
|
||||
case 'l':
|
||||
currAbsCoord[0] =
|
||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
|
||||
currAbsCoord[1] =
|
||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
|
||||
break;
|
||||
case 'Z':
|
||||
case 'z':
|
||||
// Overlapping in Z should be handled in another rule
|
||||
currAbsCoord = [startPoint[0], startPoint[1]];
|
||||
_resetStartPoint = true;
|
||||
break;
|
||||
case 'C':
|
||||
currAbsCoord[0] = seg[5];
|
||||
currAbsCoord[1] = seg[6];
|
||||
} else if (cmd === 'a') {
|
||||
currAbsCoord[0] =
|
||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[6];
|
||||
currAbsCoord[1] =
|
||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[7];
|
||||
} else if (cmd === 'A') {
|
||||
currAbsCoord[0] = seg[6];
|
||||
currAbsCoord[1] = seg[7];
|
||||
} else if (cmd === 's') {
|
||||
currAbsCoord[0] =
|
||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
|
||||
currAbsCoord[1] =
|
||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
|
||||
} else if (cmd === 'S') {
|
||||
currAbsCoord[0] = seg[1];
|
||||
currAbsCoord[1] = seg[2];
|
||||
} else if (cmd === 't') {
|
||||
currAbsCoord[0] =
|
||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
|
||||
currAbsCoord[1] =
|
||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
|
||||
} else if (cmd === 'T') {
|
||||
currAbsCoord[0] = seg[1];
|
||||
currAbsCoord[1] = seg[2];
|
||||
} else if (cmd === 'c') {
|
||||
break;
|
||||
case 'c':
|
||||
currAbsCoord[0] =
|
||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[5];
|
||||
currAbsCoord[1] =
|
||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[6];
|
||||
} else if (cmd === 'Q') {
|
||||
break;
|
||||
case 'A':
|
||||
currAbsCoord[0] = seg[6];
|
||||
currAbsCoord[1] = seg[7];
|
||||
break;
|
||||
case 'a':
|
||||
currAbsCoord[0] =
|
||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[6];
|
||||
currAbsCoord[1] =
|
||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[7];
|
||||
break;
|
||||
case 's':
|
||||
currAbsCoord[0] =
|
||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[1];
|
||||
currAbsCoord[1] =
|
||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[2];
|
||||
break;
|
||||
case 'S':
|
||||
currAbsCoord[0] = seg[1];
|
||||
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];
|
||||
} else if (cmd === 'q') {
|
||||
break;
|
||||
case 'q':
|
||||
currAbsCoord[0] =
|
||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + seg[3];
|
||||
currAbsCoord[1] =
|
||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + seg[4];
|
||||
} else if (zCommands.includes(cmd)) {
|
||||
// Overlapping in Z should be handled in another rule
|
||||
currAbsCoord = [startPoint[0], startPoint[1]];
|
||||
_resetStartPoint = true;
|
||||
} else {
|
||||
break;
|
||||
default:
|
||||
throw new Error(`"${cmd}" command not handled`);
|
||||
}
|
||||
|
||||
@ -774,32 +793,22 @@ export default {
|
||||
};
|
||||
|
||||
const collinearSegments = getCollinearSegments(iconPath),
|
||||
pathDIndex = getPathDIndex($.html());
|
||||
collinearSegments.forEach((segment) => {
|
||||
pathDIndex = getPathDIndex(ast.source);
|
||||
for (const segment of collinearSegments) {
|
||||
let errorMsg = `Collinear segment "${iconPath.substring(
|
||||
segment.start,
|
||||
segment.end,
|
||||
)}" found`;
|
||||
if (segment.chained) {
|
||||
let readableChain = iconPath.substring(
|
||||
segment.chainStart,
|
||||
segment.chainEnd,
|
||||
let readableChain = maybeShortenedWithEllipsis(
|
||||
iconPath.substring(segment.chainStart, segment.chainEnd),
|
||||
);
|
||||
if (readableChain.length > 20) {
|
||||
readableChain = `${readableChain.substring(0, 20)}...`;
|
||||
}
|
||||
errorMsg += ` in chain "${readableChain}"`;
|
||||
}
|
||||
errorMsg += ` at index ${
|
||||
segment.start + pathDIndex
|
||||
} (should be removed)`;
|
||||
reporter.error(errorMsg);
|
||||
});
|
||||
|
||||
if (collinearSegments.length) {
|
||||
if (updateIgnoreFile) {
|
||||
ignoreIcon(reporter.name, iconPath, $);
|
||||
}
|
||||
}
|
||||
},
|
||||
(reporter, $, ast) => {
|
||||
@ -812,7 +821,8 @@ export default {
|
||||
);
|
||||
} else {
|
||||
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';
|
||||
|
||||
const iconPath = $.find('path').attr('d');
|
||||
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find negative zeros inside path
|
||||
const negativeZeroMatches = Array.from(
|
||||
@ -831,22 +838,22 @@ export default {
|
||||
);
|
||||
if (negativeZeroMatches.length) {
|
||||
// Calculate the index for each match in the file
|
||||
const svgFileContent = $.html();
|
||||
const pathDIndex = getPathDIndex(svgFileContent);
|
||||
const pathDIndex = getPathDIndex(ast.source);
|
||||
|
||||
negativeZeroMatches.forEach((match) => {
|
||||
for (const match of negativeZeroMatches) {
|
||||
const negativeZeroFileIndex = match.index + pathDIndex;
|
||||
const previousChar = svgFileContent[negativeZeroFileIndex - 1];
|
||||
const previousChar = ast.source[negativeZeroFileIndex - 1];
|
||||
const replacement = '0123456789'.includes(previousChar)
|
||||
? ' 0'
|
||||
: '0';
|
||||
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';
|
||||
|
||||
const iconPath = $.find('path').attr('d');
|
||||
@ -866,7 +873,8 @@ export default {
|
||||
Math.abs(devianceY) > iconTolerance
|
||||
) {
|
||||
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) {
|
||||
ignoreIcon(reporter.name, iconPath, $);
|
||||
@ -878,20 +886,24 @@ export default {
|
||||
|
||||
const iconPath = $.find('path').attr('d');
|
||||
|
||||
const validPathFormatRegex = /^[Mm][MmZzLlHhVvCcSsQqTtAaEe0-9-,.\s]+$/;
|
||||
if (!validPathFormatRegex.test(iconPath)) {
|
||||
if (!svgPathRegexp.test(iconPath)) {
|
||||
let errorMsg = 'Invalid path format',
|
||||
reason;
|
||||
|
||||
if (!/^[Mm]/.test(iconPath)) {
|
||||
// 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}`);
|
||||
}
|
||||
|
||||
const validPathCharacters = 'MmZzLlHhVvCcSsQqTtAaEe0123456789-,. ',
|
||||
const validPathCharacters = svgPathRegexp.source.replace(
|
||||
/[\[\]+^$]/g,
|
||||
'',
|
||||
),
|
||||
invalidCharactersMsgs = [],
|
||||
pathDIndex = getPathDIndex($.html());
|
||||
pathDIndex = getPathDIndex(ast.source);
|
||||
|
||||
for (let [i, char] of Object.entries(iconPath)) {
|
||||
if (validPathCharacters.indexOf(char) === -1) {
|
||||
@ -919,8 +931,8 @@ export default {
|
||||
const reason =
|
||||
`found a closing "path" tag at index ${ast.source.indexOf(
|
||||
'</path>',
|
||||
)}.` +
|
||||
" The path should be self-closing, use '/>' instead of '></path>'.";
|
||||
)}. The path should be self-closing,` +
|
||||
' use "/>" instead of "></path>".';
|
||||
reporter.error(`Invalid SVG content format: ${reason}`);
|
||||
}
|
||||
},
|
||||
|
@ -34,6 +34,7 @@ const generateSdkMts = async () => {
|
||||
' --declaration --emitDeclarationOnly --allowJs --removeComments',
|
||||
);
|
||||
} catch (error) {
|
||||
await fs.writeFile(sdkMjs, originalSdkMjsContent);
|
||||
console.log(
|
||||
`Error ${error.status} generating Typescript` +
|
||||
` definitions: '${error.message}'`,
|
||||
|
2
sdk.mjs
2
sdk.mjs
@ -112,7 +112,7 @@ export const titleToHtmlFriendly = (brandTitle) =>
|
||||
*/
|
||||
export const htmlFriendlyToTitle = (htmlFriendlyTitle) =>
|
||||
htmlFriendlyTitle
|
||||
.replace(/&#([0-9]+);/g, (_, num) => String.fromCharCode(parseInt(num)))
|
||||
.replace(/&#([0-9]+);/g, (_, num) => String.fromCodePoint(parseInt(num)))
|
||||
.replace(
|
||||
/&(quot|amp|lt|gt);/g,
|
||||
(_, ref) => ({ quot: '"', amp: '&', lt: '<', gt: '>' }[ref]),
|
||||
|
Loading…
Reference in New Issue
Block a user