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

Add a new linter rule for ineffective commands in paths (#4262)

* Add a new linter rule for invalid command in path

* Add other ineffective paths

* Imrpove path validation linter

* Relative h/v have a single corrdinate

* Concatenation instead of Template Use string

* Rebase on develop

* Add absolute direction check

* Use absolute coordinate to compare with previous

* Rebase

* Rename arrays with plural

* Loop back until we have missing coordinates to compare

* Rebase on develop

* Rename to ineffective-segments

* Rebase on develop

* Manage relative Bézier Curve and Shorthand curve

* Manage absolute Bézier Curve and Shorthand curve

* Fix Shorthand curve verification

* Rebase on develop

* Fix from review

* fix brain
This commit is contained in:
Alexandre Paradis 2020-12-13 14:29:01 -05:00 committed by GitHub
parent 7318a00b86
commit a6d90e24e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 181 additions and 10 deletions

File diff suppressed because one or more lines are too long

View File

@ -2,6 +2,7 @@ const fs = require('fs');
const data = require("./_data/simple-icons.json");
const { htmlFriendlyToTitle } = require("./scripts/utils.js");
const svgPath = require("svgpath");
const parsePath = require("svgpath/lib/path_parse");
const { svgPathBbox } = require("svg-path-bbox");
@ -32,6 +33,11 @@ function sortObjectByValue(obj) {
.reduce((r, k) => Object.assign(r, { [k]: obj[k] }), {});
}
function removeLeadingZeros(number) {
// convert 0.03 to '.03'
return number.toString().replace(/^(-?)(0)(\.?.+)/, '$1$3');
}
if (updateIgnoreFile) {
process.on('exit', () => {
// ensure object output order is consistent due to async svglint processing
@ -49,7 +55,7 @@ if (updateIgnoreFile) {
}
function isIgnored(linterName, path) {
return iconIgnored[linterName].hasOwnProperty(path);
return iconIgnored[linterName] && iconIgnored[linterName].hasOwnProperty(path);
}
function ignoreIcon(linterName, path, $) {
@ -138,7 +144,7 @@ module.exports = {
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) {
return;
}
const { segments } = parsePath(iconPath);
const segmentParts = segments.flat().filter((num) => (typeof num === 'number'));
@ -163,6 +169,137 @@ module.exports = {
}
}
},
function(reporter, $, ast) {
reporter.name = "ineffective-segments";
const iconPath = $.find("path").attr("d");
if (!updateIgnoreFile && isIgnored(reporter.name, iconPath)) {
return;
}
const { segments } = parsePath(iconPath);
const { segments: absSegments } = svgPath(iconPath).abs().unshort();
const lowerMovementCommands = ['m', 'l'];
const lowerDirectionCommands = ['h', 'v'];
const lowerCurveCommand = 'c';
const lowerShorthandCurveCommand = 's';
const lowerCurveCommands = [lowerCurveCommand, lowerShorthandCurveCommand];
const upperMovementCommands = ['M', 'L'];
const upperHorDirectionCommand = 'H';
const upperVerDirectionCommand = 'V';
const upperDirectionCommands = [upperHorDirectionCommand, upperVerDirectionCommand];
const upperCurveCommand = 'C';
const upperShorthandCurveCommand = 'S';
const upperCurveCommands = [upperCurveCommand, upperShorthandCurveCommand];
const curveCommands = [...lowerCurveCommands, ...upperCurveCommands];
const commands = [...lowerMovementCommands, ...lowerDirectionCommands, ...upperMovementCommands, ...upperDirectionCommands, ...curveCommands];
const getInvalidSegments = ([command, x1Coord, y1Coord, ...rest], index) => {
if (commands.includes(command)) {
// Relative directions (h or v) having a length of 0
if (lowerDirectionCommands.includes(command) && x1Coord === 0) {
return true;
}
// Relative movement (m or l) having a distance of 0
if (lowerMovementCommands.includes(command) && x1Coord === 0 && y1Coord === 0) {
return true;
}
if (lowerCurveCommands.includes(command) && x1Coord === 0 && y1Coord === 0) {
const [x2Coord, y2Coord] = rest;
if (
// Relative shorthand curve (s) having a control point of 0
command === lowerShorthandCurveCommand ||
// Relative bézier curve (c) having control points of 0
(command === lowerCurveCommand && x2Coord === 0 && y2Coord === 0)
) {
return true;
}
}
if (index > 0) {
let [yPrevCoord, xPrevCoord, ...prevRest] = [...absSegments[index - 1]].reverse();
// 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;
let idx = index;
while (--idx > 0 && (xPrevCoord === undefined || yPrevCoord === undefined)) {
let [yPrevCoordDeep, xPrevCoordDeep, ...rest] = [...absSegments[idx]].reverse();
// 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 (upperVerDirectionCommand === xPrevCoordDeep) {
xPrevCoordDeep = undefined;
}
if (xPrevCoord === undefined && xPrevCoordDeep !== undefined) {
xPrevCoord = xPrevCoordDeep;
}
if (yPrevCoord === undefined && yPrevCoordDeep !== undefined) {
yPrevCoord = yPrevCoordDeep;
}
}
}
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
if (upperShorthandCurveCommand === command && x1Coord === xPrevCoord && y1Coord === yPrevCoord && x1Coord === x2Coord && y1Coord === y2Coord) {
return true;
}
// 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 && y1Coord === yPrevCoord && x2Coord === xCoord && y2Coord === yCoord) {
return true;
}
}
return (
// 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
(upperVerDirectionCommand === command && x1Coord === yPrevCoord) ||
// Absolute movement (M or L) having the same coordinate as the previous segment
(upperMovementCommands.includes(command) && x1Coord === xPrevCoord && y1Coord === yPrevCoord)
);
}
}
};
const invalidSegments = segments.filter(getInvalidSegments);
if (invalidSegments.length) {
invalidSegments.forEach(([command, x1Coord, y1Coord, ...rest]) => {
let readableSegment = `${command}${x1Coord}`,
resolutionTip = 'should be removed';
if (y1Coord !== undefined) {
readableSegment += ` ${y1Coord}`;
}
if (curveCommands.includes(command)) {
const [x2Coord, y2Coord, xCoord, yCoord] = rest;
readableSegment += `, ${x2Coord} ${y2Coord}`;
if (yCoord !== undefined) {
readableSegment += `, ${xCoord} ${yCoord}`;
}
if (command === lowerShorthandCurveCommand && (x2Coord !== 0 || y2Coord !== 0)) {
resolutionTip = `should be "l${removeLeadingZeros(x2Coord)} ${removeLeadingZeros(y2Coord)}" or removed`;
}
if (command === upperShorthandCurveCommand) {
resolutionTip = `should be "L${removeLeadingZeros(x2Coord)} ${removeLeadingZeros(y2Coord)}" or removed`;
}
if (command === lowerCurveCommand && (xCoord !== 0 || yCoord !== 0)) {
resolutionTip = `should be "l${removeLeadingZeros(xCoord)} ${removeLeadingZeros(yCoord)}" or removed`;
}
if (command === upperCurveCommand) {
resolutionTip = `should be "L${removeLeadingZeros(xCoord)} ${removeLeadingZeros(yCoord)}" or removed`;
}
}
reporter.error(`Ineffective segment "${readableSegment}" in path (${resolutionTip}).`);
});
if (updateIgnoreFile) {
ignoreIcon(reporter.name, iconPath, $);
}
}
},
function(reporter, $, ast) {
reporter.name = "extraneous";