Stricter rules for JSDoc documentation blocks (#11881)

This commit is contained in:
Álvaro Mondéjar Rubio 2024-09-29 16:28:34 +02:00 committed by GitHub
parent 1a5a37cc2d
commit 5183e3c06b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 150 additions and 136 deletions

View File

@ -2,7 +2,7 @@
"prettier": true, "prettier": true,
"space": 2, "space": 2,
"plugins": ["import"], "plugins": ["import"],
"extends": ["plugin:jsdoc/recommended"], "extends": ["plugin:jsdoc/recommended-error"],
"rules": { "rules": {
"sort-imports": [ "sort-imports": [
"error", "error",
@ -29,7 +29,21 @@
} }
], ],
"no-console": ["error", {"allow": ["warn", "error"]}], "no-console": ["error", {"allow": ["warn", "error"]}],
"jsdoc/require-file-overview": "error" "no-warning-comments": [
"warn",
{
"terms": ["fixme", "xxx"]
}
],
"jsdoc/require-file-overview": "error",
"jsdoc/require-description": "error",
"jsdoc/no-bad-blocks": "error",
"jsdoc/no-blank-blocks": "error",
"jsdoc/no-blank-block-descriptions": "error",
"jsdoc/check-syntax": "error",
"jsdoc/require-asterisk-prefix": "error",
"jsdoc/require-description-complete-sentence": "error",
"jsdoc/require-hyphen-before-param-description": ["error", "never"]
}, },
"overrides": [ "overrides": [
{ {

View File

@ -91,7 +91,7 @@
"editorconfig-checker": "5.1.5", "editorconfig-checker": "5.1.5",
"esbuild": "0.20.2", "esbuild": "0.20.2",
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.29.1",
"eslint-plugin-jsdoc": "48.2.8", "eslint-plugin-jsdoc": "50.3.0",
"fake-diff": "1.0.0", "fake-diff": "1.0.0",
"fast-fuzzy": "1.12.0", "fast-fuzzy": "1.12.0",
"get-relative-luminance": "1.0.0", "get-relative-luminance": "1.0.0",

View File

@ -41,8 +41,9 @@ const licenseTypes =
); );
/** /**
* @param {string} input URL input * Whether an input is a valid URL.
* @returns {Promise<boolean|string>} Whether the input is a valid URL * @param {string} input URL input.
* @returns {Promise<boolean|string>} Whether the input is a valid URL.
*/ */
const isValidURL = async (input) => { const isValidURL = async (input) => {
const regex = await urlRegex(); const regex = await urlRegex();
@ -50,15 +51,17 @@ const isValidURL = async (input) => {
}; };
/** /**
* @param {string} input Hex color * Whether an input is a valid hex color.
* @returns {boolean|string} Whether the input is a valid hex color * @param {string} input Hex color.
* @returns {boolean|string} Whether the input is a valid hex color.
*/ */
const isValidHexColor = (input) => const isValidHexColor = (input) =>
HEX_REGEX.test(input) || 'Must be a valid hex code.'; HEX_REGEX.test(input) || 'Must be a valid hex code.';
/** /**
* @param {string} input New icon input * Whether an icon is not already in the dataset.
* @returns {boolean} Whether the icon is new * @param {string} input New icon input.
* @returns {boolean} Whether the icon is new.
*/ */
const isNewIcon = (input) => const isNewIcon = (input) =>
!iconsData.icons.some( !iconsData.icons.some(
@ -67,8 +70,9 @@ const isNewIcon = (input) =>
); );
/** /**
* @param {string} input Color input * Compute a preview of a color to use in prompt background.
* @returns {string} Preview of the color * @param {string} input Color input.
* @returns {string} Preview of the color.
*/ */
const previewHexColor = (input) => { const previewHexColor = (input) => {
const color = normalizeColor(input); const color = normalizeColor(input);

View File

@ -13,6 +13,11 @@ const __dirname = getDirnameFromImportMeta(import.meta.url);
const rootDirectory = path.resolve(__dirname, '..', '..'); const rootDirectory = path.resolve(__dirname, '..', '..');
const files = ['index.js', 'index.mjs', 'index.d.ts', 'sdk.js']; const files = ['index.js', 'index.mjs', 'index.d.ts', 'sdk.js'];
/**
* Check if a file exists.
* @param {string} fpath File path to check.
* @returns {Promise<boolean>} True if the file exists, false otherwise.
*/
const fileExists = async (fpath) => { const fileExists = async (fpath) => {
try { try {
await fs.access(fpath); await fs.access(fpath);

View File

@ -53,18 +53,20 @@ const icons = await getIconsData();
const iconObjectTemplate = await fs.readFile(iconObjectTemplateFile, UTF8); const iconObjectTemplate = await fs.readFile(iconObjectTemplateFile, UTF8);
/** /**
* @param {string} value The value to escape * Escape a string for use in a JavaScript string.
* @returns {string} The escaped value * @param {string} value The value to escape.
* @returns {string} The escaped value.
*/ */
const escape = (value) => { const escape = (value) => {
return value.replaceAll(/(?<!\\)'/g, "\\'"); return value.replaceAll(/(?<!\\)'/g, "\\'");
}; };
/** /**
* @param {License} license The license object or URL * Converts a license object to a URL if the URL is not defined.
* @returns {License} The license object with a URL * @param {License} license The license object or URL.
* @returns {License} The license object with a URL.
*/ */
const licenseToObject = (license) => { const licenseToString = (license) => {
if (license.url === undefined) { if (license.url === undefined) {
license.url = `https://spdx.org/licenses/${license.type}`; license.url = `https://spdx.org/licenses/${license.type}`;
} }
@ -74,8 +76,8 @@ const licenseToObject = (license) => {
/** /**
* Converts an icon object to a JavaScript object. * Converts an icon object to a JavaScript object.
* @param {IconDataAndObject} icon The icon object * @param {IconDataAndObject} icon The icon object.
* @returns {string} The JavaScript object * @returns {string} The JavaScript object.
*/ */
const iconToJsObject = (icon) => { const iconToJsObject = (icon) => {
return format( return format(
@ -89,14 +91,15 @@ const iconToJsObject = (icon) => {
icon.guidelines ? `\n guidelines: '${escape(icon.guidelines)}',` : '', icon.guidelines ? `\n guidelines: '${escape(icon.guidelines)}',` : '',
icon.license === undefined icon.license === undefined
? '' ? ''
: `\n license: ${JSON.stringify(licenseToObject(icon.license))},`, : `\n license: ${JSON.stringify(licenseToString(icon.license))},`,
); );
}; };
/** /**
* @param {string} filepath The path to the file to write * Write JavaScript content to a file.
* @param {string} rawJavaScript The raw JavaScript content to write to the file * @param {string} filepath The path to the file to write.
* @param {EsBuildTransformOptions | null} options The options to pass to esbuild * @param {string} rawJavaScript The raw JavaScript content to write to the file.
* @param {EsBuildTransformOptions | null} options The options to pass to esbuild.
*/ */
const writeJs = async (filepath, rawJavaScript, options = null) => { const writeJs = async (filepath, rawJavaScript, options = null) => {
options = options === null ? {minify: true} : options; options = options === null ? {minify: true} : options;
@ -105,8 +108,9 @@ const writeJs = async (filepath, rawJavaScript, options = null) => {
}; };
/** /**
* @param {string} filepath The path to the file to write * Write TypeScript content to a file.
* @param {string} rawTypeScript The raw TypeScript content to write to the file * @param {string} filepath The path to the file to write.
* @param {string} rawTypeScript The raw TypeScript content to write to the file.
*/ */
const writeTs = async (filepath, rawTypeScript) => { const writeTs = async (filepath, rawTypeScript) => {
await fs.writeFile(filepath, rawTypeScript); await fs.writeFile(filepath, rawTypeScript);

View File

@ -1,8 +1,7 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* @file * @file
* Linters for the package that can't easily be implemented in the existing * Linters for the package that can't easily be implemented in the existing ones.
* linters (e.g. jsonlint/svglint).
*/ */
/** /**
@ -22,18 +21,18 @@ import {collator, getIconsDataString, normalizeNewlines} from '../../sdk.mjs';
*/ */
const TESTS = { const TESTS = {
/** /**
* Tests whether our icons are in alphabetical order * Tests whether our icons are in alphabetical order.
* @param {{icons: IconsData}} data Icons data * @param {{icons: IconsData}} data Icons data.
* @returns {string|undefined} Error message or undefined * @returns {string|undefined} Error message or undefined.
*/ */
alphabetical(data) { alphabetical(data) {
/** /**
* Collects invalid alphabet ordered icons * Collects invalid alphabet ordered icons.
* @param {IconData[]} invalidEntries Invalid icons reference * @param {IconData[]} invalidEntries Invalid icons reference.
* @param {IconData} icon Icon to check * @param {IconData} icon Icon to check.
* @param {number} index Index of the icon * @param {number} index Index of the icon.
* @param {IconData[]} array Array of icons * @param {IconData[]} array Array of icons.
* @returns {IconData[]} Invalid icons * @returns {IconData[]} Invalid icons.
*/ */
const collector = (invalidEntries, icon, index, array) => { const collector = (invalidEntries, icon, index, array) => {
if (index > 0) { if (index > 0) {
@ -54,9 +53,9 @@ const TESTS = {
}; };
/** /**
* Format an icon for display in the error message * Format an icon for display in the error message.
* @param {IconData} icon Icon to format * @param {IconData} icon Icon to format.
* @returns {string} Formatted icon * @returns {string} Formatted icon.
*/ */
const format = (icon) => { const format = (icon) => {
if (icon.slug) { if (icon.slug) {
@ -89,32 +88,32 @@ const TESTS = {
checkUrl(data) { checkUrl(data) {
/** /**
* Check if an URL has a redundant trailing slash. * Check if an URL has a redundant trailing slash.
* @param {URL} $url URL instance * @param {URL} $url URL instance.
* @param {string} url Original URL string * @param {string} url Original URL string.
* @returns {boolean} Whether the URL has a redundant trailing slash * @returns {boolean} Whether the URL has a redundant trailing slash.
*/ */
const hasRedundantTrailingSlash = ($url, url) => url === $url.origin + '/'; const hasRedundantTrailingSlash = ($url, url) => url === $url.origin + '/';
/** /**
* Check if an URL is static wikimedia asset URL. * Check if an URL is static wikimedia asset URL.
* @param {URL} $url URL instance * @param {URL} $url URL instance.
* @returns {boolean} Whether the URL is static wikimedia asset URL * @returns {boolean} Whether the URL is static wikimedia asset URL.
*/ */
const isStaticWikimediaAssetUrl = ($url) => const isStaticWikimediaAssetUrl = ($url) =>
$url.hostname === 'upload.wikimedia.org'; $url.hostname === 'upload.wikimedia.org';
/** /**
* Check if an URL is raw GitHub asset URL. * Check if an URL is raw GitHub asset URL.
* @param {URL} $url URL instance * @param {URL} $url URL instance.
* @returns {boolean} Whether the URL is raw GitHub asset URL * @returns {boolean} Whether the URL is raw GitHub asset URL.
*/ */
const isRawGithubAssetUrl = ($url) => const isRawGithubAssetUrl = ($url) =>
$url.hostname === 'raw.githubusercontent.com'; $url.hostname === 'raw.githubusercontent.com';
/** /**
* Check if an URL is a GitHub URL. * Check if an URL is a GitHub URL.
* @param {URL} $url URL instance * @param {URL} $url URL instance.
* @returns {boolean} Whether the URL is a GitHub URL * @returns {boolean} Whether the URL is a GitHub URL.
*/ */
const isGitHubUrl = ($url) => $url.hostname === 'github.com'; const isGitHubUrl = ($url) => $url.hostname === 'github.com';
@ -135,8 +134,8 @@ const TESTS = {
/** /**
* Check if an URL is a permanent GitHub URL for a file. * Check if an URL is a permanent GitHub URL for a file.
* @param {string} url URL string * @param {string} url URL string.
* @returns {boolean} Whether the URL is a GitHub URL for a file * @returns {boolean} Whether the URL is a GitHub URL for a file.
*/ */
const isPermalinkGitHubFileUrl = (url) => permalinkGitHubRegex.test(url); const isPermalinkGitHubFileUrl = (url) => permalinkGitHubRegex.test(url);
@ -154,7 +153,6 @@ const TESTS = {
if (icon.license !== undefined && Object.hasOwn(icon.license, 'url')) { if (icon.license !== undefined && Object.hasOwn(icon.license, 'url')) {
allUrlFields.push([ allUrlFields.push([
false, false,
// eslint-disable-next-line no-warning-comments
// TODO: `hasOwn` is not currently supported by TS. // TODO: `hasOwn` is not currently supported by TS.
// See https://github.com/microsoft/TypeScript/issues/44253 // See https://github.com/microsoft/TypeScript/issues/44253
/** @type {string} */ /** @type {string} */

View File

@ -19,7 +19,7 @@ const disclaimerFile = path.resolve(rootDirectory, 'DISCLAIMER.md');
/** /**
* Reformat a file. * Reformat a file.
* @param {string} filePath Path to the file * @param {string} filePath Path to the file.
*/ */
const reformat = async (filePath) => { const reformat = async (filePath) => {
const fileContent = await readFile(filePath, 'utf8'); const fileContent = await readFile(filePath, 'utf8');

View File

@ -17,6 +17,7 @@ const packageJsonFile = path.resolve(rootDirectory, 'package.json');
const readmeFile = path.resolve(rootDirectory, 'README.md'); const readmeFile = path.resolve(rootDirectory, 'README.md');
/** /**
* Get the major version number from a semantic version string.
* @param {string} semVersion A semantic version string. * @param {string} semVersion A semantic version string.
* @returns {number} The major version number. * @returns {number} The major version number.
*/ */
@ -35,6 +36,7 @@ const getManifest = async () => {
}; };
/** /**
* Update the version number in the README.md.
* @param {number} majorVersion The major version number. * @param {number} majorVersion The major version number.
*/ */
const updateVersionInReadmeIfNecessary = async (majorVersion) => { const updateVersionInReadmeIfNecessary = async (majorVersion) => {

View File

@ -56,9 +56,9 @@ const generateSdkMts = async () => {
/** /**
* We must remove the duplicated export types that tsc generates from * We must remove the duplicated export types that tsc generates from
* JSDoc `typedef` comments. * JSDoc `typedef` comments.
* See {@link https://github.com/microsoft/TypeScript/issues/46011} * See {@link https://github.com/microsoft/TypeScript/issues/46011}.
* @param {string} content Content of the file * @param {string} content Content of the file.
* @returns {string} The content without duplicated export types * @returns {string} The content without duplicated export types.
*/ */
const removeDuplicatedExportTypes = (content) => { const removeDuplicatedExportTypes = (content) => {
const newContent = []; const newContent = [];

69
sdk.mjs
View File

@ -42,16 +42,16 @@ export const SVG_PATH_REGEX = /^m[-mzlhvcsqtae\d,. ]+$/i;
/** /**
* Get the directory name where this file is located from `import.meta.url`, * Get the directory name where this file is located from `import.meta.url`,
* equivalent to the `__dirname` global variable in CommonJS. * equivalent to the `__dirname` global variable in CommonJS.
* @param {string} importMetaUrl import.meta.url * @param {string} importMetaUrl Relative `import.meta.url` value of the caller.
* @returns {string} Directory name in which this file is located * @returns {string} Directory name in which this file is located.
*/ */
export const getDirnameFromImportMeta = (importMetaUrl) => export const getDirnameFromImportMeta = (importMetaUrl) =>
path.dirname(fileURLToPath(importMetaUrl)); path.dirname(fileURLToPath(importMetaUrl));
/** /**
* Build a regex to validate HTTPs URLs. * Build a regex to validate HTTPs URLs.
* @param {string} jsonschemaPath Path to the *.jsonschema.json* file * @param {string} jsonschemaPath Path to the *.jsonschema.json* file.
* @returns {Promise<RegExp>} Regex to validate HTTPs URLs * @returns {Promise<RegExp>} Regex to validate HTTPs URLs.
*/ */
export const urlRegex = async ( export const urlRegex = async (
jsonschemaPath = path.join( jsonschemaPath = path.join(
@ -68,22 +68,22 @@ export const urlRegex = async (
/** /**
* Get the slug/filename for an icon. * Get the slug/filename for an icon.
* @param {IconData} icon The icon data as it appears in *_data/simple-icons.json* * @param {IconData} icon The icon data as it appears in *_data/simple-icons.json*.
* @returns {string} The slug/filename for the icon * @returns {string} The slug/filename for the icon.
*/ */
export const getIconSlug = (icon) => icon.slug || titleToSlug(icon.title); export const getIconSlug = (icon) => icon.slug || titleToSlug(icon.title);
/** /**
* Extract the path from an icon SVG content. * Extract the path from an icon SVG content.
* @param {string} svg The icon SVG content * @param {string} svg The icon SVG content.
* @returns {string} The path from the icon SVG content * @returns {string} The path from the icon SVG content.
*/ */
export const svgToPath = (svg) => svg.split('"', 8)[7]; export const svgToPath = (svg) => svg.split('"', 8)[7];
/** /**
* Converts a brand title into a slug/filename. * Converts a brand title into a slug/filename.
* @param {string} title The title to convert * @param {string} title The title to convert.
* @returns {string} The slug/filename for the title * @returns {string} The slug/filename for the title.
*/ */
export const titleToSlug = (title) => export const titleToSlug = (title) =>
title title
@ -97,8 +97,8 @@ export const titleToSlug = (title) =>
/** /**
* Converts a slug into a variable name that can be exported. * Converts a slug into a variable name that can be exported.
* @param {string} slug The slug to convert * @param {string} slug The slug to convert.
* @returns {string} The variable name for the slug * @returns {string} The variable name for the slug.
*/ */
export const slugToVariableName = (slug) => { export const slugToVariableName = (slug) => {
const slugFirstLetter = slug[0].toUpperCase(); const slugFirstLetter = slug[0].toUpperCase();
@ -108,8 +108,8 @@ export const slugToVariableName = (slug) => {
/** /**
* Converts a brand title as defined in *_data/simple-icons.json* into a brand * Converts a brand title as defined in *_data/simple-icons.json* into a brand
* title in HTML/SVG friendly format. * title in HTML/SVG friendly format.
* @param {string} brandTitle The title to convert * @param {string} brandTitle The title to convert.
* @returns {string} The brand title in HTML/SVG friendly format * @returns {string} The brand title in HTML/SVG friendly format.
*/ */
export const titleToHtmlFriendly = (brandTitle) => export const titleToHtmlFriendly = (brandTitle) =>
brandTitle brandTitle
@ -126,9 +126,9 @@ export const titleToHtmlFriendly = (brandTitle) =>
/** /**
* Converts a brand title in HTML/SVG friendly format into a brand title (as * Converts a brand title in HTML/SVG friendly format into a brand title (as
* it is seen in *_data/simple-icons.json*) * it is seen in *_data/simple-icons.json*).
* @param {string} htmlFriendlyTitle The title to convert * @param {string} htmlFriendlyTitle The title to convert.
* @returns {string} The brand title in HTML/SVG friendly format * @returns {string} The brand title in HTML/SVG friendly format.
*/ */
export const htmlFriendlyToTitle = (htmlFriendlyTitle) => export const htmlFriendlyToTitle = (htmlFriendlyTitle) =>
htmlFriendlyTitle htmlFriendlyTitle
@ -138,17 +138,18 @@ export const htmlFriendlyToTitle = (htmlFriendlyTitle) =>
.replaceAll( .replaceAll(
/&(quot|amp|lt|gt);/g, /&(quot|amp|lt|gt);/g,
/** /**
* @param {string} _ Full match * Replace HTML entity references with their respective decoded characters.
* @param {'quot' | 'amp' | 'lt' | 'gt'} reference Reference to replace * @param {string} _ Full match.
* @returns {string} Replacement for the reference * @param {'quot' | 'amp' | 'lt' | 'gt'} reference Reference to replace.
* @returns {string} Replacement for the reference.
*/ */
(_, reference) => ({quot: '"', amp: '&', lt: '<', gt: '>'})[reference], (_, reference) => ({quot: '"', amp: '&', lt: '<', gt: '>'})[reference],
); );
/** /**
* Get path of *_data/simple-icons.json*. * Get path of *_data/simple-icons.json*.
* @param {string} rootDirectory Path to the root directory of the project * @param {string} rootDirectory Path to the root directory of the project.
* @returns {string} Path of *_data/simple-icons.json* * @returns {string} Path of *_data/simple-icons.json*.
*/ */
export const getIconDataPath = ( export const getIconDataPath = (
rootDirectory = getDirnameFromImportMeta(import.meta.url), rootDirectory = getDirnameFromImportMeta(import.meta.url),
@ -158,8 +159,8 @@ export const getIconDataPath = (
/** /**
* Get contents of *_data/simple-icons.json*. * Get contents of *_data/simple-icons.json*.
* @param {string} rootDirectory Path to the root directory of the project * @param {string} rootDirectory Path to the root directory of the project.
* @returns {Promise<string>} Content of *_data/simple-icons.json* * @returns {Promise<string>} Content of *_data/simple-icons.json*.
*/ */
export const getIconsDataString = ( export const getIconsDataString = (
rootDirectory = getDirnameFromImportMeta(import.meta.url), rootDirectory = getDirnameFromImportMeta(import.meta.url),
@ -169,8 +170,8 @@ export const getIconsDataString = (
/** /**
* Get icons data as object from *_data/simple-icons.json*. * Get icons data as object from *_data/simple-icons.json*.
* @param {string} rootDirectory Path to the root directory of the project * @param {string} rootDirectory Path to the root directory of the project.
* @returns {Promise<IconData[]>} Icons data as array from *_data/simple-icons.json* * @returns {Promise<IconData[]>} Icons data as array from *_data/simple-icons.json*.
*/ */
export const getIconsData = async ( export const getIconsData = async (
rootDirectory = getDirnameFromImportMeta(import.meta.url), rootDirectory = getDirnameFromImportMeta(import.meta.url),
@ -181,8 +182,8 @@ export const getIconsData = async (
/** /**
* Replace Windows newline characters by Unix ones. * Replace Windows newline characters by Unix ones.
* @param {string} text The text to replace * @param {string} text The text to replace.
* @returns {string} The text with Windows newline characters replaced by Unix ones * @returns {string} The text with Windows newline characters replaced by Unix ones.
*/ */
export const normalizeNewlines = (text) => { export const normalizeNewlines = (text) => {
return text.replaceAll('\r\n', '\n'); return text.replaceAll('\r\n', '\n');
@ -190,8 +191,8 @@ export const normalizeNewlines = (text) => {
/** /**
* Convert non-6-digit hex color to 6-digit with the character `#` stripped. * Convert non-6-digit hex color to 6-digit with the character `#` stripped.
* @param {string} text The color text * @param {string} text The color text.
* @returns {string} The color text in 6-digit hex format * @returns {string} The color text in 6-digit hex format.
*/ */
export const normalizeColor = (text) => { export const normalizeColor = (text) => {
let color = text.replace('#', '').toUpperCase(); let color = text.replace('#', '').toUpperCase();
@ -207,8 +208,8 @@ export const normalizeColor = (text) => {
/** /**
* Get information about third party extensions from the README table. * Get information about third party extensions from the README table.
* @param {string} readmePath Path to the README file * @param {string} readmePath Path to the README file.
* @returns {Promise<ThirdPartyExtension[]>} Information about third party extensions * @returns {Promise<ThirdPartyExtension[]>} Information about third party extensions.
*/ */
export const getThirdPartyExtensions = async ( export const getThirdPartyExtensions = async (
readmePath = path.join( readmePath = path.join(
@ -258,8 +259,8 @@ export const getThirdPartyExtensions = async (
/** /**
* Get information about third party libraries from the README table. * Get information about third party libraries from the README table.
* @param {string} readmePath Path to the README file * @param {string} readmePath Path to the README file.
* @returns {Promise<ThirdPartyExtension[]>} Information about third party libraries * @returns {Promise<ThirdPartyExtension[]>} Information about third party libraries.
*/ */
export const getThirdPartyLibraries = async ( export const getThirdPartyLibraries = async (
readmePath = path.join( readmePath = path.join(

View File

@ -51,8 +51,9 @@ const ignoreFile = './.svglint-ignored.json';
const iconIgnored = updateIgnoreFile ? {} : svglintIgnores; const iconIgnored = updateIgnoreFile ? {} : svglintIgnores;
/** /**
* @param {{ [key: string]: any }} object Object to sort by key * Sort an object alphabetically by key converting it to an array.
* @returns {{ [key: string]: any }} Object sorted by key * @param {{ [key: string]: any }} object Object to sort by key.
* @returns {{ [key: string]: any }} Object sorted by key.
*/ */
const sortObjectByKey = (object) => { const sortObjectByKey = (object) => {
return Object.fromEntries( return Object.fromEntries(
@ -63,8 +64,9 @@ const sortObjectByKey = (object) => {
}; };
/** /**
* @param {{ [key: string]: any }} object Object to sort by value * Sort an object alphabetically by value converting it to an array.
* @returns {{ [key: string]: any }} Object sorted by value * @param {{ [key: string]: any }} object Object to sort by value.
* @returns {{ [key: string]: any }} Object sorted by value.
*/ */
const sortObjectByValue = (object) => { const sortObjectByValue = (object) => {
return Object.fromEntries( return Object.fromEntries(
@ -222,7 +224,7 @@ if (updateIgnoreFile) {
* Check if an icon is ignored by a linter rule. * Check if an icon is ignored by a linter rule.
* @param {string} linterRule The name of the linter rule. * @param {string} linterRule The name of the linter rule.
* @param {string} path SVG path of the icon. * @param {string} path SVG path of the icon.
* @returns {boolean} Whether the icon is ignored by the linter rule * @returns {boolean} Whether the icon is ignored by the linter rule.
*/ */
const isIgnored = (linterRule, path) => { const isIgnored = (linterRule, path) => {
return ( return (
@ -234,7 +236,7 @@ const isIgnored = (linterRule, path) => {
* Ignore an icon for a linter rule. * Ignore an icon for a linter rule.
* @param {string} linterRule The name of the linter rule. * @param {string} linterRule The name of the linter rule.
* @param {string} path SVG path of the icon. * @param {string} path SVG path of the icon.
* @param {Cheerio} $ The SVG object * @param {Cheerio} $ The SVG object.
*/ */
const ignoreIcon = (linterRule, path, $) => { const ignoreIcon = (linterRule, path, $) => {
iconIgnored[linterRule] ||= {}; iconIgnored[linterRule] ||= {};
@ -520,11 +522,14 @@ const config = {
const segments = getIconPathSegments(iconPath); const segments = getIconPathSegments(iconPath);
/** @type {import('svg-path-segments').Segment[]} */ /** @type {import('svg-path-segments').Segment[]} */
// eslint-disable-next-line no-warning-comments
// TODO: svgpath does not includes the `segments` property on the interface, // TODO: svgpath does not includes the `segments` property on the interface,
// see https://github.com/fontello/svgpath/pull/67/files // see https://github.com/fontello/svgpath/pull/67/files
// @ts-ignore //
const absSegments = svgpath(iconPath).abs().unshort().segments; /** @typedef {[string, ...number[]]} Segment */
/** @type {Segment[]} */
const absSegments =
// @ts-ignore
svgpath(iconPath).abs().unshort().segments;
const lowerMovementCommands = ['m', 'l']; const lowerMovementCommands = ['m', 'l'];
const lowerDirectionCommands = ['h', 'v']; const lowerDirectionCommands = ['h', 'v'];
@ -608,8 +613,11 @@ const config = {
].reverse(); ].reverse();
// If the previous command was a direction one, // If the previous command was a direction one,
// we need to iterate back until we find the missing coordinates // we need to iterate back until we find the missing coordinates
// @ts-ignore
if (upperDirectionCommands.includes(xPreviousCoord)) { if (upperDirectionCommands.includes(xPreviousCoord)) {
// @ts-ignore
xPreviousCoord = undefined; xPreviousCoord = undefined;
// @ts-ignore
yPreviousCoord = undefined; yPreviousCoord = undefined;
let index_ = index; let index_ = index;
while ( while (
@ -624,12 +632,14 @@ const config = {
// we need to consider the single coordinate as x // we need to consider the single coordinate as x
if (upperHorDirectionCommand === xPreviousCoordDeep) { if (upperHorDirectionCommand === xPreviousCoordDeep) {
xPreviousCoordDeep = yPreviousCoordDeep; xPreviousCoordDeep = yPreviousCoordDeep;
// @ts-ignore
yPreviousCoordDeep = undefined; yPreviousCoordDeep = undefined;
} }
// If the previous command was a vertical movement, // If the previous command was a vertical movement,
// we need to consider the single coordinate as y // we need to consider the single coordinate as y
if (upperVersionDirectionCommand === xPreviousCoordDeep) { if (upperVersionDirectionCommand === xPreviousCoordDeep) {
// @ts-ignore
xPreviousCoordDeep = undefined; xPreviousCoordDeep = undefined;
} }
@ -791,10 +801,8 @@ const config = {
// Next switch cases have been ordered by frequency // Next switch cases have been ordered by frequency
// of occurrence in the SVG paths of the icons // of occurrence in the SVG paths of the icons
case 'M': { case 'M': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[0] = parms[1]; currentAbsCoord[0] = parms[1];
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[1] = parms[2]; currentAbsCoord[1] = parms[2];
// SVG 1.1: // SVG 1.1:
@ -808,10 +816,8 @@ const config = {
} }
case 'm': { case 'm': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[1]; currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[1];
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[2]; currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[2];
if (seg.chain === undefined || seg.chain.start === seg.start) { if (seg.chain === undefined || seg.chain.start === seg.start) {
@ -822,48 +828,40 @@ const config = {
} }
case 'H': { case 'H': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[0] = parms[1]; currentAbsCoord[0] = parms[1];
break; break;
} }
case 'h': { case 'h': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[1]; currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[1];
break; break;
} }
case 'V': { case 'V': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[1] = parms[1]; currentAbsCoord[1] = parms[1];
break; break;
} }
case 'v': { case 'v': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[1]; currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[1];
break; break;
} }
case 'L': { case 'L': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[0] = parms[1]; currentAbsCoord[0] = parms[1];
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[1] = parms[2]; currentAbsCoord[1] = parms[2];
break; break;
} }
case 'l': { case 'l': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[1]; currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[1];
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[2]; currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[2];
break; break;
@ -871,108 +869,88 @@ const config = {
case 'Z': case 'Z':
case 'z': { case 'z': {
// eslint-disable-next-line no-warning-comments
// TODO: Overlapping in Z should be handled in another rule // TODO: Overlapping in Z should be handled in another rule
// @ts-ignore
currentAbsCoord = [startPoint[0], startPoint[1]]; currentAbsCoord = [startPoint[0], startPoint[1]];
_resetStartPoint = true; _resetStartPoint = true;
break; break;
} }
case 'C': { case 'C': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[0] = parms[5]; currentAbsCoord[0] = parms[5];
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[1] = parms[6]; currentAbsCoord[1] = parms[6];
break; break;
} }
case 'c': { case 'c': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[5]; currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[5];
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[6]; currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[6];
break; break;
} }
case 'A': { case 'A': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[0] = parms[6]; currentAbsCoord[0] = parms[6];
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[1] = parms[7]; currentAbsCoord[1] = parms[7];
break; break;
} }
case 'a': { case 'a': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[6]; currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[6];
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[7]; currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[7];
break; break;
} }
case 's': { case 's': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[1]; currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[1];
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[2]; currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[2];
break; break;
} }
case 'S': { case 'S': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[0] = parms[1]; currentAbsCoord[0] = parms[1];
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[1] = parms[2]; currentAbsCoord[1] = parms[2];
break; break;
} }
case 't': { case 't': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[1]; currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[1];
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[2]; currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[2];
break; break;
} }
case 'T': { case 'T': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[0] = parms[1]; currentAbsCoord[0] = parms[1];
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[1] = parms[2]; currentAbsCoord[1] = parms[2];
break; break;
} }
case 'Q': { case 'Q': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[0] = parms[3]; currentAbsCoord[0] = parms[3];
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[1] = parms[4]; currentAbsCoord[1] = parms[4];
break; break;
} }
case 'q': { case 'q': {
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[3]; currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[3];
/** @type {number} */
// @ts-ignore // @ts-ignore
currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[4]; currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[4];
break; break;
@ -990,6 +968,7 @@ const config = {
_resetStartPoint = false; _resetStartPoint = false;
} }
// @ts-ignore
_nextInStraightLine = straightLineCommands.includes(nextCmd); _nextInStraightLine = straightLineCommands.includes(nextCmd);
const _exitingStraightLine = const _exitingStraightLine =
_inStraightLine && !_nextInStraightLine; _inStraightLine && !_nextInStraightLine;
@ -1006,6 +985,7 @@ const config = {
// Get collinear coordinates // Get collinear coordinates
for (let p = 1; p < currentLine.length - 1; p++) { for (let p = 1; p < currentLine.length - 1; p++) {
const _collinearCoord = collinear( const _collinearCoord = collinear(
// @ts-ignore
currentLine[p - 1][0], currentLine[p - 1][0],
currentLine[p - 1][1], currentLine[p - 1][1],
currentLine[p][0], currentLine[p][0],

View File

@ -2,7 +2,7 @@
* @file Custom mocha reporter. * @file Custom mocha reporter.
* *
* Serves to clear the console after the test run is finished. * Serves to clear the console after the test run is finished.
* See {@link https://github.com/mochajs/mocha/issues/2312} * See {@link https://github.com/mochajs/mocha/issues/2312}.
*/ */
const {reporters, Runner} = require('mocha'); const {reporters, Runner} = require('mocha');
@ -11,11 +11,16 @@ const {EVENT_RUN_END} = Runner.constants;
class EvenMoreMin extends reporters.Base { class EvenMoreMin extends reporters.Base {
/** /**
* @param {import('mocha').Runner} runner Mocha test runner * Construct a new `EvenMoreMin` reporter.
* @param {import('mocha').Runner} runner Mocha test runner.
*/ */
constructor(runner) { constructor(runner) {
super(runner); super(runner);
runner.once(EVENT_RUN_END, () => this.epilogue()); runner.once(EVENT_RUN_END, () => {
// TODO: mocha's base reporters are not typed
// @ts-ignore
return this.epilogue();
});
} }
} }

View File

@ -20,9 +20,10 @@ const iconsDirectory = path.resolve(
/** /**
* Checks if icon data matches a subject icon. * Checks if icon data matches a subject icon.
* @param {import('../sdk.d.ts').IconData} icon Icon data * @param {import('../sdk.d.ts').IconData} icon Icon data.
* @param {import('../types.d.ts').SimpleIcon} subject Icon object to check against icon data * @param {import('../types.d.ts').SimpleIcon} subject
* @param {string} slug Icon data slug * Icon object to check against icon data.
* @param {string} slug Icon data slug.
*/ */
export const testIcon = (icon, subject, slug) => { export const testIcon = (icon, subject, slug) => {
const svgPath = path.resolve(iconsDirectory, `${slug}.svg`); const svgPath = path.resolve(iconsDirectory, `${slug}.svg`);