mirror of
https://github.com/Mibew/simple-icons.git
synced 2025-05-03 17:43:09 +03:00
Add XO linter (#10643)
This commit is contained in:
parent
d66bdb1380
commit
bf69b6dee0
8
.github/workflows/create-release.yml
vendored
8
.github/workflows/create-release.yml
vendored
@ -54,13 +54,13 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm i --ignore-scripts --no-audit --no-fund
|
run: npm i --ignore-scripts --no-audit --no-fund
|
||||||
- name: Update major version in CDN URLs
|
- name: Update major version in CDN URLs
|
||||||
run: node ./scripts/release/update-cdn-urls.js
|
run: ./scripts/release/update-cdn-urls.js
|
||||||
- name: Update SVGs count milestone
|
- name: Update SVGs count milestone
|
||||||
run: node ./scripts/release/update-svgs-count.js
|
run: ./scripts/release/update-svgs-count.js
|
||||||
- name: Update slugs table
|
- name: Update slugs table
|
||||||
run: node ./scripts/release/update-slugs-table.js
|
run: ./scripts/release/update-slugs-table.js
|
||||||
- name: Update SDK Typescript definitions
|
- name: Update SDK Typescript definitions
|
||||||
run: node ./scripts/release/update-sdk-ts-defs.js
|
run: ./scripts/release/update-sdk-ts-defs.js
|
||||||
- name: Commit version bump
|
- name: Commit version bump
|
||||||
uses: stefanzweifel/git-auto-commit-action@v5
|
uses: stefanzweifel/git-auto-commit-action@v5
|
||||||
with:
|
with:
|
||||||
|
6
.github/workflows/publish.yml
vendored
6
.github/workflows/publish.yml
vendored
@ -43,9 +43,9 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm i --ignore-scripts --no-audit --no-fund
|
run: npm i --ignore-scripts --no-audit --no-fund
|
||||||
- name: Reformat to regular markdown
|
- name: Reformat to regular markdown
|
||||||
run: node ./scripts/release/reformat-markdown.js "${{ steps.get-version.outputs.version }}"
|
run: ./scripts/release/reformat-markdown.js "${{ steps.get-version.outputs.version }}"
|
||||||
- name: Update SDK Typescript definitions
|
- name: Update SDK Typescript definitions
|
||||||
run: node ./scripts/release/update-sdk-ts-defs.js
|
run: ./scripts/release/update-sdk-ts-defs.js
|
||||||
- name: Build NodeJS package
|
- name: Build NodeJS package
|
||||||
run: npm run build
|
run: npm run build
|
||||||
- name: Deploy to NPM
|
- name: Deploy to NPM
|
||||||
@ -65,7 +65,7 @@ jobs:
|
|||||||
- id: get-version
|
- id: get-version
|
||||||
uses: ./.github/actions/get-version
|
uses: ./.github/actions/get-version
|
||||||
- name: Reformat to regular markdown
|
- name: Reformat to regular markdown
|
||||||
run: node ./scripts/release/reformat-markdown.js "${{ steps.get-version.outputs.version }}"
|
run: ./scripts/release/reformat-markdown.js "${{ steps.get-version.outputs.version }}"
|
||||||
- name: Configure GIT credentials
|
- name: Configure GIT credentials
|
||||||
run: |
|
run: |
|
||||||
git config user.name "${GITHUB_ACTOR}"
|
git config user.name "${GITHUB_ACTOR}"
|
||||||
|
@ -6,12 +6,3 @@
|
|||||||
|
|
||||||
# We use our own formatting for the data files.
|
# We use our own formatting for the data files.
|
||||||
_data/simple-icons.json
|
_data/simple-icons.json
|
||||||
|
|
||||||
# JavaScript templates are invalid JavaScript so cannot be formatted.
|
|
||||||
scripts/build/templates/*.js
|
|
||||||
|
|
||||||
# Generated JavaScript files don't need to be formatted
|
|
||||||
index.js
|
|
||||||
index.mjs
|
|
||||||
index.d.ts
|
|
||||||
sdk.js
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"singleQuote": true
|
"singleQuote": true,
|
||||||
|
"bracketSpacing": false
|
||||||
}
|
}
|
||||||
|
49
.xo-config.json
Normal file
49
.xo-config.json
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"prettier": true,
|
||||||
|
"space": 2,
|
||||||
|
"plugins": ["import"],
|
||||||
|
"rules": {
|
||||||
|
"n/no-unsupported-features": "off",
|
||||||
|
"n/no-unsupported-features/node-builtins": "off",
|
||||||
|
"n/file-extension-in-import": "off",
|
||||||
|
"sort-imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"ignoreCase": false,
|
||||||
|
"ignoreDeclarationSort": true,
|
||||||
|
"ignoreMemberSort": false,
|
||||||
|
"memberSyntaxSortOrder": ["none", "all", "multiple", "single"],
|
||||||
|
"allowSeparatedGroups": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"import/no-named-as-default": "off",
|
||||||
|
"import/extensions": "off",
|
||||||
|
"import/order": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"groups": ["builtin", "external", "parent", "sibling", "index"],
|
||||||
|
"alphabetize": {
|
||||||
|
"order": "asc",
|
||||||
|
"caseInsensitive": true
|
||||||
|
},
|
||||||
|
"warnOnUnassignedImports": true,
|
||||||
|
"newlines-between": "never"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["sdk.mjs", "sdk.d.ts"],
|
||||||
|
"nodeVersion": ">=14"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"scripts/**/*",
|
||||||
|
"tests/**/*",
|
||||||
|
"svglint.config.mjs",
|
||||||
|
"svgo.config.mjs"
|
||||||
|
],
|
||||||
|
"nodeVersion": ">=18"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
26
package.json
26
package.json
@ -88,6 +88,7 @@
|
|||||||
"chalk": "5.3.0",
|
"chalk": "5.3.0",
|
||||||
"editorconfig-checker": "5.1.5",
|
"editorconfig-checker": "5.1.5",
|
||||||
"esbuild": "0.19.4",
|
"esbuild": "0.19.4",
|
||||||
|
"eslint-plugin-import": "2.29.1",
|
||||||
"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",
|
||||||
@ -97,23 +98,24 @@
|
|||||||
"markdown-link-check": "3.11.2",
|
"markdown-link-check": "3.11.2",
|
||||||
"mocha": "10.2.0",
|
"mocha": "10.2.0",
|
||||||
"named-html-entities-json": "1.0.0",
|
"named-html-entities-json": "1.0.0",
|
||||||
"prettier": "3.0.3",
|
|
||||||
"svg-path-bbox": "1.2.5",
|
"svg-path-bbox": "1.2.5",
|
||||||
"svg-path-segments": "1.0.0",
|
"svg-path-segments": "1.0.0",
|
||||||
"svglint": "2.4.0",
|
"svglint": "2.4.0",
|
||||||
"svgo": "3.0.2",
|
"svgo": "3.0.2",
|
||||||
"svgpath": "2.6.0",
|
"svgpath": "2.6.0",
|
||||||
"typescript": "5.2.2"
|
"typescript": "5.2.2",
|
||||||
|
"xo": "0.58.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node scripts/build/package.js",
|
"build": "./scripts/build/package.js",
|
||||||
"clean": "node scripts/build/clean.js",
|
"clean": "./scripts/build/clean.js",
|
||||||
"format": "prettier --cache --write .",
|
"format": "prettier --cache --write --ignore-unknown '**/*.!(js|jsx|mjs|cjs|ts|tsx|mts|cts|svg)' && xo --fix",
|
||||||
"lint": "npm run ourlint && npm run jslint && npm run jsonlint && npm run svglint && npm run wslint",
|
"lint": "npm run ourlint && npm run prettierlint && npm run jslint && npm run jsonlint && npm run svglint && npm run wslint",
|
||||||
"ourlint": "node scripts/lint/ourlint.js",
|
"ourlint": "./scripts/lint/ourlint.js",
|
||||||
"jslint": "prettier --cache --check .",
|
"prettierlint": "prettier --cache --check --ignore-unknown '**/*.!(js|jsx|mjs|cjs|ts|tsx|mts|cts|svg)'",
|
||||||
"jsonlint": "node scripts/lint/jsonlint.js",
|
"jslint": "xo",
|
||||||
"svglint": "svglint --ci $npm_config_icons",
|
"jsonlint": "./scripts/lint/jsonlint.js",
|
||||||
|
"svglint": "svglint --ci $npm_config_icons --config svglint.config.mjs",
|
||||||
"wslint": "editorconfig-checker",
|
"wslint": "editorconfig-checker",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"prepublishOnly": "npm run build",
|
"prepublishOnly": "npm run build",
|
||||||
@ -121,8 +123,8 @@
|
|||||||
"test": "mocha tests --reporter tests/min-reporter.cjs --inline-diffs",
|
"test": "mocha tests --reporter tests/min-reporter.cjs --inline-diffs",
|
||||||
"pretest": "npm run prepublishOnly",
|
"pretest": "npm run prepublishOnly",
|
||||||
"posttest": "npm run postpublish",
|
"posttest": "npm run postpublish",
|
||||||
"get-filename": "node scripts/get-filename.js",
|
"get-filename": "./scripts/get-filename.js",
|
||||||
"add-icon-data": "node scripts/add-icon-data.js"
|
"add-icon-data": "./scripts/add-icon-data.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.12.18"
|
"node": ">=0.12.18"
|
||||||
|
23
scripts/add-icon-data.js
Normal file → Executable file
23
scripts/add-icon-data.js
Normal file → Executable file
@ -1,22 +1,23 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
|
import {ExitPromptError, checkbox, confirm, input} from '@inquirer/prompts';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { input, confirm, checkbox, ExitPromptError } from '@inquirer/prompts';
|
|
||||||
import autocomplete from 'inquirer-autocomplete-standalone';
|
|
||||||
import getRelativeLuminance from 'get-relative-luminance';
|
|
||||||
import {search} from 'fast-fuzzy';
|
import {search} from 'fast-fuzzy';
|
||||||
|
import getRelativeLuminance from 'get-relative-luminance';
|
||||||
|
import autocomplete from 'inquirer-autocomplete-standalone';
|
||||||
import {
|
import {
|
||||||
URL_REGEX,
|
URL_REGEX,
|
||||||
collator,
|
collator,
|
||||||
getIconsDataString,
|
getIconsDataString,
|
||||||
titleToSlug,
|
|
||||||
normalizeColor,
|
normalizeColor,
|
||||||
|
titleToSlug,
|
||||||
} from '../sdk.mjs';
|
} from '../sdk.mjs';
|
||||||
import {getJsonSchemaData, writeIconsData} from './utils.js';
|
import {getJsonSchemaData, writeIconsData} from './utils.js';
|
||||||
|
|
||||||
const iconsData = JSON.parse(await getIconsDataString());
|
const iconsData = JSON.parse(await getIconsDataString());
|
||||||
const jsonSchema = await getJsonSchemaData();
|
const jsonSchema = await getJsonSchemaData();
|
||||||
|
|
||||||
const HEX_REGEX = /^#?[a-f0-9]{3,8}$/i;
|
const HEX_REGEX = /^#?[a-f\d]{3,8}$/i;
|
||||||
|
|
||||||
const aliasTypes = ['aka', 'old'].map((key) => ({
|
const aliasTypes = ['aka', 'old'].map((key) => ({
|
||||||
name: `${key} (${jsonSchema.definitions.brand.properties.aliases.properties[key].description})`,
|
name: `${key} (${jsonSchema.definitions.brand.properties.aliases.properties[key].description})`,
|
||||||
@ -35,7 +36,7 @@ const isValidHexColor = (input) =>
|
|||||||
HEX_REGEX.test(input) || 'Must be a valid hex code.';
|
HEX_REGEX.test(input) || 'Must be a valid hex code.';
|
||||||
|
|
||||||
const isNewIcon = (input) =>
|
const isNewIcon = (input) =>
|
||||||
!iconsData.icons.find(
|
!iconsData.icons.some(
|
||||||
(icon) =>
|
(icon) =>
|
||||||
icon.title === input || titleToSlug(icon.title) === titleToSlug(input),
|
icon.title === input || titleToSlug(icon.title) === titleToSlug(input),
|
||||||
) || 'This icon title or slug already exists.';
|
) || 'This icon title or slug already exists.';
|
||||||
@ -83,7 +84,7 @@ try {
|
|||||||
? {
|
? {
|
||||||
type: await autocomplete({
|
type: await autocomplete({
|
||||||
message: "What is the icon's license?",
|
message: "What is the icon's license?",
|
||||||
source: async (input) => {
|
async source(input) {
|
||||||
input = (input || '').trim();
|
input = (input || '').trim();
|
||||||
return input
|
return input
|
||||||
? search(input, licenseTypes, {keySelector: (x) => x.value})
|
? search(input, licenseTypes, {keySelector: (x) => x.value})
|
||||||
@ -107,12 +108,14 @@ try {
|
|||||||
}).then(async (aliases) => {
|
}).then(async (aliases) => {
|
||||||
const result = {};
|
const result = {};
|
||||||
for (const alias of aliases) {
|
for (const alias of aliases) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
result[alias] = await input({
|
result[alias] = await input({
|
||||||
message: `What ${alias} aliases would you like to add? (separate with commas)`,
|
message: `What ${alias} aliases would you like to add? (separate with commas)`,
|
||||||
}).then((aliases) =>
|
}).then((aliases) =>
|
||||||
aliases.split(',').map((alias) => alias.trim()),
|
aliases.split(',').map((alias) => alias.trim()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
})
|
})
|
||||||
: undefined,
|
: undefined,
|
||||||
@ -136,11 +139,11 @@ try {
|
|||||||
console.log(chalk.red('\nAborted.'));
|
console.log(chalk.red('\nAborted.'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
if (err instanceof ExitPromptError) {
|
if (error instanceof ExitPromptError) {
|
||||||
console.log(chalk.red('\nAborted.'));
|
console.log(chalk.red('\nAborted.'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw err;
|
throw error;
|
||||||
}
|
}
|
||||||
|
20
scripts/build/clean.js
Normal file → Executable file
20
scripts/build/clean.js
Normal file → Executable file
@ -1,10 +1,12 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* @fileoverview
|
* @fileoverview
|
||||||
* Clean files built by the build process.
|
* Clean files built by the build process.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import process from 'node:process';
|
||||||
import {getDirnameFromImportMeta} from '../../sdk.mjs';
|
import {getDirnameFromImportMeta} from '../../sdk.mjs';
|
||||||
|
|
||||||
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
||||||
@ -12,8 +14,12 @@ 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'];
|
||||||
|
|
||||||
const fileExists = (fpath) =>
|
const fileExists = (fpath) =>
|
||||||
new Promise((r) => fs.access(fpath, fs.constants.F_OK, (e) => r(!e)));
|
fs
|
||||||
|
.access(fpath, fs.constants.F_OK)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
|
|
||||||
|
try {
|
||||||
Promise.all(
|
Promise.all(
|
||||||
files.map(async (file) => {
|
files.map(async (file) => {
|
||||||
const filepath = path.join(rootDirectory, file);
|
const filepath = path.join(rootDirectory, file);
|
||||||
@ -21,9 +27,11 @@ Promise.all(
|
|||||||
console.error(`File ${file} does not exist, skipping...`);
|
console.error(`File ${file} does not exist, skipping...`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return fs.promises.unlink(filepath);
|
|
||||||
|
return fs.unlink(filepath);
|
||||||
}),
|
}),
|
||||||
).catch((error) => {
|
);
|
||||||
console.error(`Error cleaning files: ${error.message}`);
|
} catch (error) {
|
||||||
|
console.error('Error cleaning files:', error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
}
|
||||||
|
57
scripts/build/package.js
Normal file → Executable file
57
scripts/build/package.js
Normal file → Executable file
@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* @fileoverview
|
* @fileoverview
|
||||||
* Simple Icons package build script.
|
* Simple Icons package build script.
|
||||||
@ -8,29 +9,32 @@ import path from 'node:path';
|
|||||||
import util from 'node:util';
|
import util from 'node:util';
|
||||||
import {transform as esbuildTransform} from 'esbuild';
|
import {transform as esbuildTransform} from 'esbuild';
|
||||||
import {
|
import {
|
||||||
|
collator,
|
||||||
|
getDirnameFromImportMeta,
|
||||||
getIconSlug,
|
getIconSlug,
|
||||||
|
getIconsData,
|
||||||
|
slugToVariableName,
|
||||||
svgToPath,
|
svgToPath,
|
||||||
titleToHtmlFriendly,
|
titleToHtmlFriendly,
|
||||||
slugToVariableName,
|
|
||||||
getIconsData,
|
|
||||||
getDirnameFromImportMeta,
|
|
||||||
collator,
|
|
||||||
} from '../../sdk.mjs';
|
} from '../../sdk.mjs';
|
||||||
|
|
||||||
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
||||||
|
|
||||||
const UTF8 = 'utf8';
|
const UTF8 = 'utf8';
|
||||||
|
|
||||||
const rootDir = path.resolve(__dirname, '..', '..');
|
const rootDirectory = path.resolve(__dirname, '..', '..');
|
||||||
const iconsDir = path.resolve(rootDir, 'icons');
|
const iconsDirectory = path.resolve(rootDirectory, 'icons');
|
||||||
const indexJsFile = path.resolve(rootDir, 'index.js');
|
const indexJsFile = path.resolve(rootDirectory, 'index.js');
|
||||||
const indexMjsFile = path.resolve(rootDir, 'index.mjs');
|
const indexMjsFile = path.resolve(rootDirectory, 'index.mjs');
|
||||||
const sdkJsFile = path.resolve(rootDir, 'sdk.js');
|
const sdkJsFile = path.resolve(rootDirectory, 'sdk.js');
|
||||||
const sdkMjsFile = path.resolve(rootDir, 'sdk.mjs');
|
const sdkMjsFile = path.resolve(rootDirectory, 'sdk.mjs');
|
||||||
const indexDtsFile = path.resolve(rootDir, 'index.d.ts');
|
const indexDtsFile = path.resolve(rootDirectory, 'index.d.ts');
|
||||||
|
|
||||||
const templatesDir = path.resolve(__dirname, 'templates');
|
const templatesDirectory = path.resolve(__dirname, 'templates');
|
||||||
const iconObjectTemplateFile = path.resolve(templatesDir, 'icon-object.js');
|
const iconObjectTemplateFile = path.resolve(
|
||||||
|
templatesDirectory,
|
||||||
|
'icon-object.js.template',
|
||||||
|
);
|
||||||
|
|
||||||
const build = async () => {
|
const build = async () => {
|
||||||
const icons = await getIconsData();
|
const icons = await getIconsData();
|
||||||
@ -38,8 +42,9 @@ const build = async () => {
|
|||||||
|
|
||||||
// Local helper functions
|
// Local helper functions
|
||||||
const escape = (value) => {
|
const escape = (value) => {
|
||||||
return value.replace(/(?<!\\)'/g, "\\'");
|
return value.replaceAll(/(?<!\\)'/g, "\\'");
|
||||||
};
|
};
|
||||||
|
|
||||||
const licenseToObject = (license) => {
|
const licenseToObject = (license) => {
|
||||||
if (license === undefined) {
|
if (license === undefined) {
|
||||||
return;
|
return;
|
||||||
@ -48,8 +53,10 @@ const build = async () => {
|
|||||||
if (license.url === undefined) {
|
if (license.url === undefined) {
|
||||||
license.url = `https://spdx.org/licenses/${license.type}`;
|
license.url = `https://spdx.org/licenses/${license.type}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return license;
|
return license;
|
||||||
};
|
};
|
||||||
|
|
||||||
const iconToObject = (icon) => {
|
const iconToObject = (icon) => {
|
||||||
return util.format(
|
return util.format(
|
||||||
iconObjectTemplate,
|
iconObjectTemplate,
|
||||||
@ -65,11 +72,13 @@ const build = async () => {
|
|||||||
: '',
|
: '',
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const writeJs = async (filepath, rawJavaScript, opts = null) => {
|
|
||||||
opts = opts === null ? { minify: true } : opts;
|
const writeJs = async (filepath, rawJavaScript, options = null) => {
|
||||||
const { code } = await esbuildTransform(rawJavaScript, opts);
|
options = options === null ? {minify: true} : options;
|
||||||
|
const {code} = await esbuildTransform(rawJavaScript, options);
|
||||||
await fs.writeFile(filepath, code);
|
await fs.writeFile(filepath, code);
|
||||||
};
|
};
|
||||||
|
|
||||||
const writeTs = async (filepath, rawTypeScript) => {
|
const writeTs = async (filepath, rawTypeScript) => {
|
||||||
await fs.writeFile(filepath, rawTypeScript);
|
await fs.writeFile(filepath, rawTypeScript);
|
||||||
};
|
};
|
||||||
@ -78,7 +87,7 @@ const build = async () => {
|
|||||||
const buildIcons = await Promise.all(
|
const buildIcons = await Promise.all(
|
||||||
icons.map(async (icon) => {
|
icons.map(async (icon) => {
|
||||||
const filename = getIconSlug(icon);
|
const filename = getIconSlug(icon);
|
||||||
const svgFilepath = path.resolve(iconsDir, `${filename}.svg`);
|
const svgFilepath = path.resolve(iconsDirectory, `${filename}.svg`);
|
||||||
icon.svg = await fs.readFile(svgFilepath, UTF8);
|
icon.svg = await fs.readFile(svgFilepath, UTF8);
|
||||||
icon.path = svgToPath(icon.svg);
|
icon.path = svgToPath(icon.svg);
|
||||||
icon.slug = filename;
|
icon.slug = filename;
|
||||||
@ -99,27 +108,27 @@ const build = async () => {
|
|||||||
iconsBarrelMjs.push(`export const ${iconExportName}=${iconObject}`);
|
iconsBarrelMjs.push(`export const ${iconExportName}=${iconObject}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// constants used in templates to reduce package size
|
// Constants used in templates to reduce package size
|
||||||
const constantsString = `const a='<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>',b='</title><path d="',c='"/></svg>';`;
|
const constantsString = `const a='<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>',b='</title><path d="',c='"/></svg>';`;
|
||||||
|
|
||||||
// write our file containing the exports of all icons in CommonJS ...
|
// Write our file containing the exports of all icons in CommonJS ...
|
||||||
const rawIndexJs = `${constantsString}module.exports={${iconsBarrelJs.join(
|
const rawIndexJs = `${constantsString}module.exports={${iconsBarrelJs.join(
|
||||||
'',
|
'',
|
||||||
)}};`;
|
)}};`;
|
||||||
await writeJs(indexJsFile, rawIndexJs);
|
await writeJs(indexJsFile, rawIndexJs);
|
||||||
// and ESM
|
// ... and ESM
|
||||||
const rawIndexMjs = constantsString + iconsBarrelMjs.join('');
|
const rawIndexMjs = constantsString + iconsBarrelMjs.join('');
|
||||||
await writeJs(indexMjsFile, rawIndexMjs);
|
await writeJs(indexMjsFile, rawIndexMjs);
|
||||||
// and create a type declaration file
|
// ... and create a type declaration file
|
||||||
const rawIndexDts = `import {SimpleIcon} from "./types";export {SimpleIcon};type I=SimpleIcon;${iconsBarrelDts.join(
|
const rawIndexDts = `import {SimpleIcon} from "./types";export {SimpleIcon};type I=SimpleIcon;${iconsBarrelDts.join(
|
||||||
'',
|
'',
|
||||||
)}`;
|
)}`;
|
||||||
await writeTs(indexDtsFile, rawIndexDts);
|
await writeTs(indexDtsFile, rawIndexDts);
|
||||||
|
|
||||||
// create a CommonJS SDK file
|
// Create a CommonJS SDK file
|
||||||
await writeJs(sdkJsFile, await fs.readFile(sdkMjsFile, UTF8), {
|
await writeJs(sdkJsFile, await fs.readFile(sdkMjsFile, UTF8), {
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
build();
|
await build();
|
||||||
|
6
scripts/get-filename.js
Normal file → Executable file
6
scripts/get-filename.js
Normal file → Executable file
@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* @fileoverview
|
* @fileoverview
|
||||||
* Script that takes a brand name as argument and outputs the corresponding
|
* Script that takes a brand name as argument and outputs the corresponding
|
||||||
@ -11,10 +12,7 @@ if (process.argv.length < 3) {
|
|||||||
console.error('Provide a brand name as argument');
|
console.error('Provide a brand name as argument');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} else {
|
} else {
|
||||||
const brandName = process.argv
|
const brandName = process.argv[2];
|
||||||
.slice(3)
|
|
||||||
.reduce((acc, arg) => `${acc} ${arg}`, process.argv[2]);
|
|
||||||
|
|
||||||
const filename = titleToSlug(brandName);
|
const filename = titleToSlug(brandName);
|
||||||
console.log(`For '${brandName}' use the file 'icons/${filename}.svg'`);
|
console.log(`For '${brandName}' use the file 'icons/${filename}.svg'`);
|
||||||
}
|
}
|
||||||
|
3
scripts/lint/jsonlint.js
Normal file → Executable file
3
scripts/lint/jsonlint.js
Normal file → Executable file
@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* @fileoverview
|
* @fileoverview
|
||||||
* CLI tool to run jsonschema on the simple-icons.json data file.
|
* CLI tool to run jsonschema on the simple-icons.json data file.
|
||||||
@ -14,7 +15,7 @@ const schema = await getJsonSchemaData();
|
|||||||
const validator = new Validator();
|
const validator = new Validator();
|
||||||
const result = validator.validate({icons}, schema);
|
const result = validator.validate({icons}, schema);
|
||||||
if (result.errors.length > 0) {
|
if (result.errors.length > 0) {
|
||||||
result.errors.forEach((error) => console.error(error));
|
for (const error of result.errors) console.error(error);
|
||||||
console.error(`Found ${result.errors.length} error(s) in simple-icons.json`);
|
console.error(`Found ${result.errors.length} error(s) in simple-icons.json`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
38
scripts/lint/ourlint.js
Normal file → Executable file
38
scripts/lint/ourlint.js
Normal file → Executable file
@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* @fileoverview
|
* @fileoverview
|
||||||
* 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
|
||||||
@ -5,9 +6,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
import { URL } from 'node:url';
|
|
||||||
import fakeDiff from 'fake-diff';
|
import fakeDiff from 'fake-diff';
|
||||||
import { getIconsDataString, normalizeNewlines, collator } from '../../sdk.mjs';
|
import {collator, getIconsDataString, normalizeNewlines} from '../../sdk.mjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains our tests so they can be isolated from each other.
|
* Contains our tests so they can be isolated from each other.
|
||||||
@ -15,39 +15,43 @@ import { getIconsDataString, normalizeNewlines, collator } from '../../sdk.mjs';
|
|||||||
*/
|
*/
|
||||||
const TESTS = {
|
const TESTS = {
|
||||||
/* Tests whether our icons are in alphabetical order */
|
/* Tests whether our icons are in alphabetical order */
|
||||||
alphabetical: (data) => {
|
alphabetical(data) {
|
||||||
const collector = (invalidEntries, icon, index, array) => {
|
const collector = (invalidEntries, icon, index, array) => {
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
const prev = array[index - 1];
|
const previous = array[index - 1];
|
||||||
const comparison = collator.compare(icon.title, prev.title);
|
const comparison = collator.compare(icon.title, previous.title);
|
||||||
if (comparison < 0) {
|
if (comparison < 0) {
|
||||||
invalidEntries.push(icon);
|
invalidEntries.push(icon);
|
||||||
} else if (comparison === 0) {
|
} else if (
|
||||||
if (prev.slug) {
|
comparison === 0 &&
|
||||||
if (!icon.slug || collator.compare(icon.slug, prev.slug) < 0) {
|
previous.slug &&
|
||||||
|
(!icon.slug || collator.compare(icon.slug, previous.slug) < 0)
|
||||||
|
) {
|
||||||
invalidEntries.push(icon);
|
invalidEntries.push(icon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
return invalidEntries;
|
return invalidEntries;
|
||||||
};
|
};
|
||||||
|
|
||||||
const format = (icon) => {
|
const format = (icon) => {
|
||||||
if (icon.slug) {
|
if (icon.slug) {
|
||||||
return `${icon.title} (${icon.slug})`;
|
return `${icon.title} (${icon.slug})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return icon.title;
|
return icon.title;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line unicorn/no-array-reduce, unicorn/no-array-callback-reference
|
||||||
const invalids = data.icons.reduce(collector, []);
|
const invalids = data.icons.reduce(collector, []);
|
||||||
if (invalids.length) {
|
if (invalids.length > 0) {
|
||||||
return `Some icons aren't in alphabetical order:
|
return `Some icons aren't in alphabetical order:
|
||||||
${invalids.map((icon) => format(icon)).join(', ')}`;
|
${invalids.map((icon) => format(icon)).join(', ')}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Check the formatting of the data file */
|
/* Check the formatting of the data file */
|
||||||
prettified: (data, dataString) => {
|
prettified(data, dataString) {
|
||||||
const normalizedDataString = normalizeNewlines(dataString);
|
const normalizedDataString = normalizeNewlines(dataString);
|
||||||
const dataPretty = `${JSON.stringify(data, null, 4)}\n`;
|
const dataPretty = `${JSON.stringify(data, null, 4)}\n`;
|
||||||
|
|
||||||
@ -58,9 +62,9 @@ const TESTS = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/* Check redundant trailing slash in URL */
|
/* Check redundant trailing slash in URL */
|
||||||
checkUrl: (data) => {
|
checkUrl(data) {
|
||||||
const hasRedundantTrailingSlash = (url) => {
|
const hasRedundantTrailingSlash = (url) => {
|
||||||
const origin = new URL(url).origin;
|
const {origin} = new global.URL(url);
|
||||||
return /^\/+$/.test(url.replace(origin, ''));
|
return /^\/+$/.test(url.replace(origin, ''));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,9 +93,11 @@ const data = JSON.parse(dataString);
|
|||||||
|
|
||||||
const errors = (
|
const errors = (
|
||||||
await Promise.all(Object.values(TESTS).map((test) => test(data, dataString)))
|
await Promise.all(Object.values(TESTS).map((test) => test(data, dataString)))
|
||||||
).filter(Boolean);
|
)
|
||||||
|
// eslint-disable-next-line unicorn/no-await-expression-member
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
errors.forEach((error) => console.error(`\u001b[31m${error}\u001b[0m`));
|
for (const error of errors) console.error(`\u001B[31m${error}\u001B[0m`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
22
scripts/release/reformat-markdown.js
Normal file → Executable file
22
scripts/release/reformat-markdown.js
Normal file → Executable file
@ -1,19 +1,21 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* @fileoverview
|
* @fileoverview
|
||||||
* Rewrite some Markdown files.
|
* Rewrite some Markdown files.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {readFile, writeFile} from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { writeFile, readFile } from 'node:fs/promises';
|
import process from 'node:process';
|
||||||
import {getDirnameFromImportMeta} from '../../sdk.mjs';
|
import {getDirnameFromImportMeta} from '../../sdk.mjs';
|
||||||
|
|
||||||
const LINKS_BRANCH = process.argv[2] || 'develop';
|
const LINKS_BRANCH = process.argv[2] || 'develop';
|
||||||
|
|
||||||
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
||||||
|
|
||||||
const rootDir = path.resolve(__dirname, '..', '..');
|
const rootDirectory = path.resolve(__dirname, '..', '..');
|
||||||
const readmeFile = path.resolve(rootDir, 'README.md');
|
const readmeFile = path.resolve(rootDirectory, 'README.md');
|
||||||
const disclaimerFile = path.resolve(rootDir, 'DISCLAIMER.md');
|
const disclaimerFile = path.resolve(rootDirectory, 'DISCLAIMER.md');
|
||||||
|
|
||||||
const reformat = async (filePath) => {
|
const reformat = async (filePath) => {
|
||||||
const fileContent = await readFile(filePath, 'utf8');
|
const fileContent = await readFile(filePath, 'utf8');
|
||||||
@ -21,17 +23,17 @@ const reformat = async (filePath) => {
|
|||||||
filePath,
|
filePath,
|
||||||
fileContent
|
fileContent
|
||||||
// Replace all CDN links with raw links
|
// Replace all CDN links with raw links
|
||||||
.replace(
|
.replaceAll(
|
||||||
/https:\/\/cdn.simpleicons.org\/(.+)\/000\/fff/g,
|
/https:\/\/cdn.simpleicons.org\/(.+)\/000\/fff/g,
|
||||||
`https://raw.githubusercontent.com/simple-icons/simple-icons/${LINKS_BRANCH}/icons/$1.svg`,
|
`https://raw.githubusercontent.com/simple-icons/simple-icons/${LINKS_BRANCH}/icons/$1.svg`,
|
||||||
)
|
)
|
||||||
// Replace all GitHub blockquotes with regular markdown
|
// Replace all GitHub blockquotes with regular markdown
|
||||||
// Reference: https://github.com/orgs/community/discussions/16925
|
// Reference: https://github.com/orgs/community/discussions/16925
|
||||||
.replace(
|
.replaceAll(
|
||||||
/\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\](?!\()/g,
|
/\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)](?!\()/g,
|
||||||
function (str, $0) {
|
function (string_, $0) {
|
||||||
const capital = $0.substr(0, 1);
|
const capital = $0.slice(0, 1);
|
||||||
const body = $0.substr(1).toLowerCase();
|
const body = $0.slice(1).toLowerCase();
|
||||||
return `**${capital + body}**`;
|
return `**${capital + body}**`;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
21
scripts/release/update-cdn-urls.js
Normal file → Executable file
21
scripts/release/update-cdn-urls.js
Normal file → Executable file
@ -1,35 +1,36 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* @fileoverview
|
* @fileoverview
|
||||||
* Updates the CDN URLs in the README.md to match the major version in the
|
* Updates the CDN URLs in the README.md to match the major version in the
|
||||||
* NPM package manifest. Does nothing if the README.md is already up-to-date.
|
* NPM package manifest. Does nothing if the README.md is already up-to-date.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import process from 'node:process';
|
|
||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import process from 'node:process';
|
||||||
import {getDirnameFromImportMeta} from '../../sdk.mjs';
|
import {getDirnameFromImportMeta} from '../../sdk.mjs';
|
||||||
|
|
||||||
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
||||||
|
|
||||||
const rootDir = path.resolve(__dirname, '..', '..');
|
const rootDirectory = path.resolve(__dirname, '..', '..');
|
||||||
const packageJsonFile = path.resolve(rootDir, 'package.json');
|
const packageJsonFile = path.resolve(rootDirectory, 'package.json');
|
||||||
const readmeFile = path.resolve(rootDir, 'README.md');
|
const readmeFile = path.resolve(rootDirectory, 'README.md');
|
||||||
|
|
||||||
const getMajorVersion = (semVerVersion) => {
|
const getMajorVersion = (semVersion) => {
|
||||||
const majorVersionAsString = semVerVersion.split('.')[0];
|
const majorVersionAsString = semVersion.split('.')[0];
|
||||||
return parseInt(majorVersionAsString);
|
return Number.parseInt(majorVersionAsString, 10);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getManifest = async () => {
|
const getManifest = async () => {
|
||||||
const manifestRaw = await fs.readFile(packageJsonFile, 'utf-8');
|
const manifestRaw = await fs.readFile(packageJsonFile, 'utf8');
|
||||||
return JSON.parse(manifestRaw);
|
return JSON.parse(manifestRaw);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateVersionInReadmeIfNecessary = async (majorVersion) => {
|
const updateVersionInReadmeIfNecessary = async (majorVersion) => {
|
||||||
let content = await fs.readFile(readmeFile, 'utf8');
|
let content = await fs.readFile(readmeFile, 'utf8');
|
||||||
|
|
||||||
content = content.replace(
|
content = content.replaceAll(
|
||||||
/simple-icons@v[0-9]+/g,
|
/simple-icons@v\d+/g,
|
||||||
`simple-icons@v${majorVersion}`,
|
`simple-icons@v${majorVersion}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
32
scripts/release/update-sdk-ts-defs.js
Normal file → Executable file
32
scripts/release/update-sdk-ts-defs.js
Normal file → Executable file
@ -1,33 +1,34 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* @fileoverview
|
* @fileoverview
|
||||||
* Updates the SDK Typescript definitions located in the file sdk.d.ts
|
* Updates the SDK Typescript definitions located in the file sdk.d.ts
|
||||||
* to match the current definitions of functions of sdk.mjs.
|
* to match the current definitions of functions of sdk.mjs.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import process from 'node:process';
|
import {execSync} from 'node:child_process';
|
||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { execSync } from 'node:child_process';
|
import process from 'node:process';
|
||||||
import {getDirnameFromImportMeta} from '../../sdk.mjs';
|
import {getDirnameFromImportMeta} from '../../sdk.mjs';
|
||||||
|
|
||||||
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
||||||
const rootDir = path.resolve(__dirname, '..', '..');
|
const rootDirectory = path.resolve(__dirname, '..', '..');
|
||||||
|
|
||||||
const sdkTs = path.resolve(rootDir, 'sdk.d.ts');
|
const sdkTs = path.resolve(rootDirectory, 'sdk.d.ts');
|
||||||
const sdkMts = path.resolve(rootDir, 'sdk.d.mts');
|
const sdkMts = path.resolve(rootDirectory, 'sdk.d.mts');
|
||||||
const sdkMjs = path.resolve(rootDir, 'sdk.mjs');
|
const sdkMjs = path.resolve(rootDirectory, 'sdk.mjs');
|
||||||
|
|
||||||
const generateSdkMts = async () => {
|
const generateSdkMts = async () => {
|
||||||
// remove temporally type definitions imported with comments
|
// Remove temporally type definitions imported with comments
|
||||||
// in sdk.mjs to avoid circular imports
|
// in sdk.mjs to avoid circular imports
|
||||||
const originalSdkMjsContent = await fs.readFile(sdkMjs, 'utf-8');
|
const originalSdkMjsContent = await fs.readFile(sdkMjs, 'utf8');
|
||||||
const tempSdkMjsContent = originalSdkMjsContent
|
const temporarySdkMjsContent = originalSdkMjsContent
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter((line) => {
|
.filter((line) => {
|
||||||
return !line.startsWith(' * @typedef {import("./sdk")');
|
return !line.startsWith(' * @typedef {import("./sdk")');
|
||||||
})
|
})
|
||||||
.join('\n');
|
.join('\n');
|
||||||
await fs.writeFile(sdkMjs, tempSdkMjsContent);
|
await fs.writeFile(sdkMjs, temporarySdkMjsContent);
|
||||||
try {
|
try {
|
||||||
execSync(
|
execSync(
|
||||||
'npx tsc sdk.mjs' +
|
'npx tsc sdk.mjs' +
|
||||||
@ -41,6 +42,7 @@ const generateSdkMts = async () => {
|
|||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.writeFile(sdkMjs, originalSdkMjsContent);
|
await fs.writeFile(sdkMjs, originalSdkMjsContent);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -49,13 +51,15 @@ const generateSdkTs = async () => {
|
|||||||
.access(sdkMts)
|
.access(sdkMts)
|
||||||
.then(() => true)
|
.then(() => true)
|
||||||
.catch(() => false);
|
.catch(() => false);
|
||||||
fileExists && (await fs.unlink(sdkMts));
|
if (fileExists) await fs.unlink(sdkMts);
|
||||||
await generateSdkMts();
|
await generateSdkMts();
|
||||||
|
|
||||||
const autogeneratedMsg = '/* The next code is autogenerated from sdk.mjs */';
|
const autogeneratedMessage =
|
||||||
|
'/* The next code is autogenerated from sdk.mjs */';
|
||||||
const newSdkTsContent =
|
const newSdkTsContent =
|
||||||
(await fs.readFile(sdkTs, 'utf-8')).split(autogeneratedMsg)[0] +
|
// eslint-disable-next-line unicorn/no-await-expression-member
|
||||||
`${autogeneratedMsg}\n\n${await fs.readFile(sdkMts, 'utf-8')}`;
|
(await fs.readFile(sdkTs, 'utf8')).split(autogeneratedMessage)[0] +
|
||||||
|
`${autogeneratedMessage}\n\n${await fs.readFile(sdkMts, 'utf8')}`;
|
||||||
|
|
||||||
await fs.writeFile(sdkTs, newSdkTsContent);
|
await fs.writeFile(sdkTs, newSdkTsContent);
|
||||||
await fs.unlink(sdkMts);
|
await fs.unlink(sdkMts);
|
||||||
|
12
scripts/release/update-slugs-table.js
Normal file → Executable file
12
scripts/release/update-slugs-table.js
Normal file → Executable file
@ -1,22 +1,23 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* @fileoverview
|
* @fileoverview
|
||||||
* Generates a MarkDown file that lists every brand name and their slug.
|
* Generates a MarkDown file that lists every brand name and their slug.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { promises as fs } from 'node:fs';
|
import fs from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import {fileURLToPath} from 'node:url';
|
import {fileURLToPath} from 'node:url';
|
||||||
import { getIconsData, getIconSlug } from '../../sdk.mjs';
|
import {getIconSlug, getIconsData} from '../../sdk.mjs';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
const rootDir = path.resolve(__dirname, '..', '..');
|
const rootDirectory = path.resolve(__dirname, '..', '..');
|
||||||
const slugsFile = path.resolve(rootDir, 'slugs.md');
|
const slugsFile = path.resolve(rootDirectory, 'slugs.md');
|
||||||
|
|
||||||
let content = `<!--
|
let content = `<!--
|
||||||
This file is automatically generated. If you want to change something, please
|
This file is automatically generated. If you want to change something, please
|
||||||
update the script at '${path.relative(rootDir, __filename)}'.
|
update the script at '${path.relative(rootDirectory, __filename)}'.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
# Simple Icons slugs
|
# Simple Icons slugs
|
||||||
@ -31,4 +32,5 @@ for (const icon of icons) {
|
|||||||
const brandSlug = getIconSlug(icon);
|
const brandSlug = getIconSlug(icon);
|
||||||
content += `| \`${brandName}\` | \`${brandSlug}\` |\n`;
|
content += `| \`${brandName}\` | \`${brandSlug}\` |\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.writeFile(slugsFile, content);
|
await fs.writeFile(slugsFile, content);
|
||||||
|
18
scripts/release/update-svgs-count.js
Normal file → Executable file
18
scripts/release/update-svgs-count.js
Normal file → Executable file
@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* @fileoverview
|
* @fileoverview
|
||||||
* Replaces the SVG count milestone "Over <NUMBER> Free SVG icons..." located
|
* Replaces the SVG count milestone "Over <NUMBER> Free SVG icons..." located
|
||||||
@ -5,32 +6,33 @@
|
|||||||
* more than the previous milestone.
|
* more than the previous milestone.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import process from 'node:process';
|
|
||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import process from 'node:process';
|
||||||
import {getDirnameFromImportMeta, getIconsData} from '../../sdk.mjs';
|
import {getDirnameFromImportMeta, getIconsData} from '../../sdk.mjs';
|
||||||
|
|
||||||
const regexMatcher = /Over\s(\d+)\s/;
|
const regexMatcher = /Over\s(\d+)\s/;
|
||||||
const updateRange = 100;
|
const updateRange = 100;
|
||||||
|
|
||||||
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
||||||
const rootDir = path.resolve(__dirname, '..', '..');
|
const rootDirectory = path.resolve(__dirname, '..', '..');
|
||||||
const readmeFile = path.resolve(rootDir, 'README.md');
|
const readmeFile = path.resolve(rootDirectory, 'README.md');
|
||||||
|
|
||||||
const readmeContent = await fs.readFile(readmeFile, 'utf-8');
|
const readmeContent = await fs.readFile(readmeFile, 'utf8');
|
||||||
|
|
||||||
let overNIconsInReadme;
|
let overNIconsInReadme;
|
||||||
try {
|
try {
|
||||||
overNIconsInReadme = parseInt(regexMatcher.exec(readmeContent)[1]);
|
overNIconsInReadme = Number.parseInt(regexMatcher.exec(readmeContent)[1], 10);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
'Failed to obtain number of SVG icons of current milestone in README:',
|
'Failed to obtain number of SVG icons of current milestone in README:',
|
||||||
err,
|
error,
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nIcons = (await getIconsData()).length;
|
const iconsData = await getIconsData();
|
||||||
|
const nIcons = iconsData.length;
|
||||||
const newNIcons = overNIconsInReadme + updateRange;
|
const newNIcons = overNIconsInReadme + updateRange;
|
||||||
|
|
||||||
if (nIcons > newNIcons) {
|
if (nIcons > newNIcons) {
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import path from 'node:path';
|
|
||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
|
import path from 'node:path';
|
||||||
import {getDirnameFromImportMeta, getIconDataPath} from '../sdk.mjs';
|
import {getDirnameFromImportMeta, getIconDataPath} from '../sdk.mjs';
|
||||||
|
|
||||||
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get JSON schema data.
|
* Get JSON schema data.
|
||||||
* @param {String} rootDir Path to the root directory of the project.
|
* @param {String} rootDirectory Path to the root directory of the project.
|
||||||
*/
|
*/
|
||||||
export const getJsonSchemaData = async (
|
export const getJsonSchemaData = async (
|
||||||
rootDir = path.resolve(__dirname, '..'),
|
rootDirectory = path.resolve(__dirname, '..'),
|
||||||
) => {
|
) => {
|
||||||
const jsonSchemaPath = path.resolve(rootDir, '.jsonschema.json');
|
const jsonSchemaPath = path.resolve(rootDirectory, '.jsonschema.json');
|
||||||
const jsonSchemaString = await fs.readFile(jsonSchemaPath, 'utf8');
|
const jsonSchemaString = await fs.readFile(jsonSchemaPath, 'utf8');
|
||||||
return JSON.parse(jsonSchemaString);
|
return JSON.parse(jsonSchemaString);
|
||||||
};
|
};
|
||||||
@ -19,14 +19,14 @@ export const getJsonSchemaData = async (
|
|||||||
/**
|
/**
|
||||||
* Write icons data to _data/simple-icons.json.
|
* Write icons data to _data/simple-icons.json.
|
||||||
* @param {Object} iconsData Icons data object.
|
* @param {Object} iconsData Icons data object.
|
||||||
* @param {String} rootDir Path to the root directory of the project.
|
* @param {String} rootDirectory Path to the root directory of the project.
|
||||||
*/
|
*/
|
||||||
export const writeIconsData = async (
|
export const writeIconsData = async (
|
||||||
iconsData,
|
iconsData,
|
||||||
rootDir = path.resolve(__dirname, '..'),
|
rootDirectory = path.resolve(__dirname, '..'),
|
||||||
) => {
|
) => {
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
getIconDataPath(rootDir),
|
getIconDataPath(rootDirectory),
|
||||||
`${JSON.stringify(iconsData, null, 4)}\n`,
|
`${JSON.stringify(iconsData, null, 4)}\n`,
|
||||||
'utf8',
|
'utf8',
|
||||||
);
|
);
|
||||||
|
14
sdk.d.ts
vendored
14
sdk.d.ts
vendored
@ -33,14 +33,14 @@ type ThirdPartyExtensionSubject = {
|
|||||||
export type Aliases = {
|
export type Aliases = {
|
||||||
aka?: string[];
|
aka?: string[];
|
||||||
dup?: DuplicateAlias[];
|
dup?: DuplicateAlias[];
|
||||||
loc?: { [key: string]: string };
|
loc?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DuplicateAlias = {
|
type DuplicateAlias = {
|
||||||
title: string;
|
title: string;
|
||||||
hex?: string;
|
hex?: string;
|
||||||
guidelines?: string;
|
guidelines?: string;
|
||||||
loc?: { [key: string]: string };
|
loc?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,8 +62,8 @@ export type IconData = {
|
|||||||
|
|
||||||
/* The next code is autogenerated from sdk.mjs */
|
/* The next code is autogenerated from sdk.mjs */
|
||||||
|
|
||||||
export const URL_REGEX: RegExp;
|
export const URL_REGEX: RegExp; // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
export const SVG_PATH_REGEX: RegExp;
|
export const SVG_PATH_REGEX: RegExp; // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
export function getDirnameFromImportMeta(importMetaUrl: string): string;
|
export function getDirnameFromImportMeta(importMetaUrl: string): string;
|
||||||
export function getIconSlug(icon: IconData): string;
|
export function getIconSlug(icon: IconData): string;
|
||||||
export function svgToPath(svg: string): string;
|
export function svgToPath(svg: string): string;
|
||||||
@ -71,9 +71,9 @@ export function titleToSlug(title: string): string;
|
|||||||
export function slugToVariableName(slug: string): string;
|
export function slugToVariableName(slug: string): string;
|
||||||
export function titleToHtmlFriendly(brandTitle: string): string;
|
export function titleToHtmlFriendly(brandTitle: string): string;
|
||||||
export function htmlFriendlyToTitle(htmlFriendlyTitle: string): string;
|
export function htmlFriendlyToTitle(htmlFriendlyTitle: string): string;
|
||||||
export function getIconDataPath(rootDir?: string): string;
|
export function getIconDataPath(rootDirectory?: string): string;
|
||||||
export function getIconsDataString(rootDir?: string): string;
|
export function getIconsDataString(rootDirectory?: string): string;
|
||||||
export function getIconsData(rootDir?: string): IconData[];
|
export function getIconsData(rootDirectory?: string): IconData[];
|
||||||
export function normalizeNewlines(text: string): string;
|
export function normalizeNewlines(text: string): string;
|
||||||
export function normalizeColor(text: string): string;
|
export function normalizeColor(text: string): string;
|
||||||
export function getThirdPartyExtensions(
|
export function getThirdPartyExtensions(
|
||||||
|
62
sdk.mjs
62
sdk.mjs
@ -3,8 +3,8 @@
|
|||||||
* Simple Icons SDK.
|
* Simple Icons SDK.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'node:path';
|
|
||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
|
import path from 'node:path';
|
||||||
import {fileURLToPath} from 'node:url';
|
import {fileURLToPath} from 'node:url';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,12 +26,12 @@ const TITLE_TO_SLUG_REPLACEMENTS = {
|
|||||||
ŧ: 't',
|
ŧ: 't',
|
||||||
};
|
};
|
||||||
|
|
||||||
const TITLE_TO_SLUG_CHARS_REGEX = RegExp(
|
const TITLE_TO_SLUG_CHARS_REGEX = new RegExp(
|
||||||
`[${Object.keys(TITLE_TO_SLUG_REPLACEMENTS).join('')}]`,
|
`[${Object.keys(TITLE_TO_SLUG_REPLACEMENTS).join('')}]`,
|
||||||
'g',
|
'g',
|
||||||
);
|
);
|
||||||
|
|
||||||
const TITLE_TO_SLUG_RANGE_REGEX = /[^a-z0-9]/g;
|
const TITLE_TO_SLUG_RANGE_REGEX = /[^a-z\d]/g;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regex to validate HTTPs URLs.
|
* Regex to validate HTTPs URLs.
|
||||||
@ -41,7 +41,7 @@ export const URL_REGEX = /^https:\/\/[^\s"']+$/;
|
|||||||
/**
|
/**
|
||||||
* Regex to validate SVG paths.
|
* Regex to validate SVG paths.
|
||||||
*/
|
*/
|
||||||
export const SVG_PATH_REGEX = /^m[-mzlhvcsqtae0-9,. ]+$/i;
|
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`,
|
||||||
@ -74,12 +74,12 @@ export const svgToPath = (svg) => svg.split('"', 8)[7];
|
|||||||
export const titleToSlug = (title) =>
|
export const titleToSlug = (title) =>
|
||||||
title
|
title
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(
|
.replaceAll(
|
||||||
TITLE_TO_SLUG_CHARS_REGEX,
|
TITLE_TO_SLUG_CHARS_REGEX,
|
||||||
(char) => TITLE_TO_SLUG_REPLACEMENTS[char],
|
(char) => TITLE_TO_SLUG_REPLACEMENTS[char],
|
||||||
)
|
)
|
||||||
.normalize('NFD')
|
.normalize('NFD')
|
||||||
.replace(TITLE_TO_SLUG_RANGE_REGEX, '');
|
.replaceAll(TITLE_TO_SLUG_RANGE_REGEX, '');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a slug into a variable name that can be exported.
|
* Converts a slug into a variable name that can be exported.
|
||||||
@ -99,12 +99,12 @@ export const slugToVariableName = (slug) => {
|
|||||||
*/
|
*/
|
||||||
export const titleToHtmlFriendly = (brandTitle) =>
|
export const titleToHtmlFriendly = (brandTitle) =>
|
||||||
brandTitle
|
brandTitle
|
||||||
.replace(/&/g, '&')
|
.replaceAll('&', '&')
|
||||||
.replace(/"/g, '"')
|
.replaceAll('"', '"')
|
||||||
.replace(/</g, '<')
|
.replaceAll('<', '<')
|
||||||
.replace(/>/g, '>')
|
.replaceAll('>', '>')
|
||||||
.replace(/./g, (char) => {
|
.replaceAll(/./g, (char) => {
|
||||||
const charCode = char.charCodeAt(0);
|
const charCode = char.codePointAt(0);
|
||||||
return charCode > 127 ? `&#${charCode};` : char;
|
return charCode > 127 ? `&#${charCode};` : char;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -116,43 +116,45 @@ export const titleToHtmlFriendly = (brandTitle) =>
|
|||||||
*/
|
*/
|
||||||
export const htmlFriendlyToTitle = (htmlFriendlyTitle) =>
|
export const htmlFriendlyToTitle = (htmlFriendlyTitle) =>
|
||||||
htmlFriendlyTitle
|
htmlFriendlyTitle
|
||||||
.replace(/&#([0-9]+);/g, (_, num) => String.fromCodePoint(parseInt(num)))
|
.replaceAll(/&#(\d+);/g, (_, number_) =>
|
||||||
.replace(
|
String.fromCodePoint(Number.parseInt(number_, 10)),
|
||||||
|
)
|
||||||
|
.replaceAll(
|
||||||
/&(quot|amp|lt|gt);/g,
|
/&(quot|amp|lt|gt);/g,
|
||||||
(_, ref) => ({ quot: '"', amp: '&', lt: '<', gt: '>' })[ref],
|
(_, reference) => ({quot: '"', amp: '&', lt: '<', gt: '>'})[reference],
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get path of *_data/simpe-icons.json*.
|
* Get path of *_data/simpe-icons.json*.
|
||||||
* @param {String} rootDir 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 = (
|
||||||
rootDir = getDirnameFromImportMeta(import.meta.url),
|
rootDirectory = getDirnameFromImportMeta(import.meta.url),
|
||||||
) => {
|
) => {
|
||||||
return path.resolve(rootDir, '_data', 'simple-icons.json');
|
return path.resolve(rootDirectory, '_data', 'simple-icons.json');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get contents of *_data/simple-icons.json*.
|
* Get contents of *_data/simple-icons.json*.
|
||||||
* @param {String} rootDir Path to the root directory of the project
|
* @param {String} rootDirectory Path to the root directory of the project
|
||||||
* @returns {String} Content of *_data/simple-icons.json*
|
* @returns {String} Content of *_data/simple-icons.json*
|
||||||
*/
|
*/
|
||||||
export const getIconsDataString = (
|
export const getIconsDataString = (
|
||||||
rootDir = getDirnameFromImportMeta(import.meta.url),
|
rootDirectory = getDirnameFromImportMeta(import.meta.url),
|
||||||
) => {
|
) => {
|
||||||
return fs.readFile(getIconDataPath(rootDir), 'utf8');
|
return fs.readFile(getIconDataPath(rootDirectory), 'utf8');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get icons data as object from *_data/simple-icons.json*.
|
* Get icons data as object from *_data/simple-icons.json*.
|
||||||
* @param {String} rootDir Path to the root directory of the project
|
* @param {String} rootDirectory Path to the root directory of the project
|
||||||
* @returns {IconData[]} Icons data as array from *_data/simple-icons.json*
|
* @returns {IconData[]} Icons data as array from *_data/simple-icons.json*
|
||||||
*/
|
*/
|
||||||
export const getIconsData = async (
|
export const getIconsData = async (
|
||||||
rootDir = getDirnameFromImportMeta(import.meta.url),
|
rootDirectory = getDirnameFromImportMeta(import.meta.url),
|
||||||
) => {
|
) => {
|
||||||
const fileContents = await getIconsDataString(rootDir);
|
const fileContents = await getIconsDataString(rootDirectory);
|
||||||
return JSON.parse(fileContents).icons;
|
return JSON.parse(fileContents).icons;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -162,7 +164,7 @@ export const getIconsData = async (
|
|||||||
* @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.replace(/\r\n/g, '\n');
|
return text.replaceAll('\r\n', '\n');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -173,10 +175,14 @@ export const normalizeNewlines = (text) => {
|
|||||||
export const normalizeColor = (text) => {
|
export const normalizeColor = (text) => {
|
||||||
let color = text.replace('#', '').toUpperCase();
|
let color = text.replace('#', '').toUpperCase();
|
||||||
if (color.length < 6) {
|
if (color.length < 6) {
|
||||||
color = [...color.slice(0, 3)].map((x) => x.repeat(2)).join('');
|
color = color
|
||||||
|
.slice(0, 3)
|
||||||
|
.map((x) => x.repeat(2))
|
||||||
|
.join('');
|
||||||
} else if (color.length > 6) {
|
} else if (color.length > 6) {
|
||||||
color = color.slice(0, 6);
|
color = color.slice(0, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
return color;
|
return color;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -201,11 +207,11 @@ export const getThirdPartyExtensions = async (
|
|||||||
module = module.split('<img src="')[0];
|
module = module.split('<img src="')[0];
|
||||||
return {
|
return {
|
||||||
module: {
|
module: {
|
||||||
name: /\[(.+)\]/.exec(module)[1],
|
name: /\[(.+)]/.exec(module)[1],
|
||||||
url: /\((.+)\)/.exec(module)[1],
|
url: /\((.+)\)/.exec(module)[1],
|
||||||
},
|
},
|
||||||
author: {
|
author: {
|
||||||
name: /\[(.+)\]/.exec(author)[1],
|
name: /\[(.+)]/.exec(author)[1],
|
||||||
url: /\((.+)\)/.exec(author)[1],
|
url: /\((.+)\)/.exec(author)[1],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
|
/* eslint complexity: off, max-depth: off */
|
||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
import {
|
|
||||||
SVG_PATH_REGEX,
|
|
||||||
getDirnameFromImportMeta,
|
|
||||||
htmlFriendlyToTitle,
|
|
||||||
collator,
|
|
||||||
} from './sdk.mjs';
|
|
||||||
import svgpath from 'svgpath';
|
|
||||||
import svgPathBbox from 'svg-path-bbox';
|
import svgPathBbox from 'svg-path-bbox';
|
||||||
import parsePath from 'svg-path-segments';
|
import parsePath from 'svg-path-segments';
|
||||||
|
import svgpath from 'svgpath';
|
||||||
|
import {
|
||||||
|
SVG_PATH_REGEX,
|
||||||
|
collator,
|
||||||
|
getDirnameFromImportMeta,
|
||||||
|
htmlFriendlyToTitle,
|
||||||
|
} from './sdk.mjs';
|
||||||
|
|
||||||
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
const __dirname = getDirnameFromImportMeta(import.meta.url);
|
||||||
const dataFile = path.join(__dirname, '_data', 'simple-icons.json');
|
const dataFile = path.join(__dirname, '_data', 'simple-icons.json');
|
||||||
@ -30,8 +31,8 @@ const svglintIgnores = JSON.parse(
|
|||||||
);
|
);
|
||||||
|
|
||||||
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 iconSize = 24;
|
const iconSize = 24;
|
||||||
const iconTargetCenter = iconSize / 2;
|
const iconTargetCenter = iconSize / 2;
|
||||||
@ -39,25 +40,29 @@ const iconFloatPrecision = 3;
|
|||||||
const iconMaxFloatPrecision = 5;
|
const iconMaxFloatPrecision = 5;
|
||||||
const iconTolerance = 0.001;
|
const iconTolerance = 0.001;
|
||||||
|
|
||||||
// set env SI_UPDATE_IGNORE to recreate the ignore file
|
// Set env SI_UPDATE_IGNORE to recreate the ignore file
|
||||||
const updateIgnoreFile = process.env.SI_UPDATE_IGNORE === 'true';
|
const updateIgnoreFile = process.env.SI_UPDATE_IGNORE === 'true';
|
||||||
const ignoreFile = './.svglint-ignored.json';
|
const ignoreFile = './.svglint-ignored.json';
|
||||||
const iconIgnored = !updateIgnoreFile ? svglintIgnores : {};
|
const iconIgnored = updateIgnoreFile ? {} : svglintIgnores;
|
||||||
|
|
||||||
const sortObjectByKey = (obj) => {
|
const sortObjectByKey = (object) => {
|
||||||
return Object.keys(obj)
|
return Object.fromEntries(
|
||||||
|
Object.keys(object)
|
||||||
.sort()
|
.sort()
|
||||||
.reduce((r, k) => Object.assign(r, { [k]: obj[k] }), {});
|
.map((k) => [k, object[k]]),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortObjectByValue = (obj) => {
|
const sortObjectByValue = (object) => {
|
||||||
return Object.keys(obj)
|
return Object.fromEntries(
|
||||||
.sort((a, b) => collator.compare(obj[a], obj[b]))
|
Object.keys(object)
|
||||||
.reduce((r, k) => Object.assign(r, { [k]: obj[k] }), {});
|
.sort((a, b) => collator.compare(object[a], object[b]))
|
||||||
|
.map((k) => [k, object[k]]),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeLeadingZeros = (number) => {
|
const removeLeadingZeros = (number) => {
|
||||||
// convert 0.03 to '.03'
|
// Convert 0.03 to '.03'
|
||||||
return number.toString().replace(/^(-?)(0)(\.?.+)/, '$1$3');
|
return number.toString().replace(/^(-?)(0)(\.?.+)/, '$1$3');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -65,6 +70,7 @@ const removeLeadingZeros = (number) => {
|
|||||||
* Given three points, returns if the middle one (x2, y2) is collinear
|
* Given three points, returns if the middle one (x2, y2) is collinear
|
||||||
* to the line formed by the two limit points.
|
* to the line formed by the two limit points.
|
||||||
**/
|
**/
|
||||||
|
// eslint-disable-next-line max-params
|
||||||
const collinear = (x1, y1, x2, y2, x3, y3) => {
|
const collinear = (x1, y1, x2, y2, x3, y3) => {
|
||||||
return x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2) === 0;
|
return x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2) === 0;
|
||||||
};
|
};
|
||||||
@ -73,15 +79,16 @@ const collinear = (x1, y1, x2, y2, x3, y3) => {
|
|||||||
* Returns the number of digits after the decimal point.
|
* Returns the number of digits after the decimal point.
|
||||||
* @param num The number of interest.
|
* @param num The number of interest.
|
||||||
*/
|
*/
|
||||||
const countDecimals = (num) => {
|
const countDecimals = (number_) => {
|
||||||
if (num && num % 1) {
|
if (number_ && number_ % 1) {
|
||||||
let [base, op, trail] = num.toExponential().split(/e([+-])/);
|
const [base, op, trail] = number_.toExponential().split(/e([+-])/);
|
||||||
let elen = parseInt(trail, 10);
|
const elen = Number.parseInt(trail, 10);
|
||||||
let idx = base.indexOf('.');
|
const index = base.indexOf('.');
|
||||||
return idx == -1
|
return index === -1
|
||||||
? elen
|
? elen
|
||||||
: base.length - idx - 1 + (op === '+' ? -elen : elen);
|
: base.length - index - 1 + (op === '+' ? -elen : elen);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -108,47 +115,49 @@ const getTitleTextIndex = (svgFileContent) => {
|
|||||||
* @param hex The hexadecimal number representation to convert.
|
* @param hex The hexadecimal number representation to convert.
|
||||||
**/
|
**/
|
||||||
const hexadecimalToDecimal = (hex) => {
|
const hexadecimalToDecimal = (hex) => {
|
||||||
let result = 0,
|
let result = 0;
|
||||||
digitValue;
|
let digitValue;
|
||||||
for (const digit of hex.toLowerCase()) {
|
for (const digit of hex.toLowerCase()) {
|
||||||
digitValue = '0123456789abcdefgh'.indexOf(digit);
|
digitValue = '0123456789abcdefgh'.indexOf(digit);
|
||||||
result = result * 16 + digitValue;
|
result = result * 16 + digitValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const maybeShortenedWithEllipsis = (str) => {
|
const maybeShortenedWithEllipsis = (string_) => {
|
||||||
return str.length > 20 ? `${str.substring(0, 20)}...` : str;
|
return string_.length > 20 ? `${string_.slice(0, 20)}...` : string_;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Memoize a function which accepts a single argument.
|
* Memoize a function which accepts a single argument.
|
||||||
* A second argument can be passed to be used as key.
|
* A second argument can be passed to be used as key.
|
||||||
*/
|
*/
|
||||||
const memoize = (func) => {
|
const memoize = (function_) => {
|
||||||
const results = {};
|
const results = {};
|
||||||
|
|
||||||
return (arg, defaultKey = null) => {
|
return (argument, defaultKey = null) => {
|
||||||
const key = defaultKey || arg;
|
const key = defaultKey || argument;
|
||||||
|
|
||||||
|
results[key] ||= function_(argument);
|
||||||
|
|
||||||
if (!results[key]) {
|
|
||||||
results[key] = func(arg);
|
|
||||||
}
|
|
||||||
return results[key];
|
return results[key];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIconPath = memoize(($icon, filepath) => $icon.find('path').attr('d'));
|
const getIconPath = memoize(($icon, _filepath) => $icon.find('path').attr('d'));
|
||||||
const getIconPathSegments = memoize((iconPath) => parsePath(iconPath));
|
const getIconPathSegments = memoize((iconPath) => parsePath(iconPath));
|
||||||
const getIconPathBbox = memoize((iconPath) => svgPathBbox(iconPath));
|
const getIconPathBbox = memoize((iconPath) => svgPathBbox(iconPath));
|
||||||
|
|
||||||
if (updateIgnoreFile) {
|
if (updateIgnoreFile) {
|
||||||
process.on('exit', async () => {
|
process.on('exit', async () => {
|
||||||
// ensure object output order is consistent due to async svglint processing
|
// Ensure object output order is consistent due to async svglint processing
|
||||||
const sorted = sortObjectByKey(iconIgnored);
|
const sorted = sortObjectByKey(iconIgnored);
|
||||||
for (const linterName in sorted) {
|
for (const linterName in sorted) {
|
||||||
|
if (linterName) {
|
||||||
sorted[linterName] = sortObjectByValue(sorted[linterName]);
|
sorted[linterName] = sortObjectByValue(sorted[linterName]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await fs.writeFile(ignoreFile, JSON.stringify(sorted, null, 2) + '\n', {
|
await fs.writeFile(ignoreFile, JSON.stringify(sorted, null, 2) + '\n', {
|
||||||
flag: 'w',
|
flag: 'w',
|
||||||
@ -158,14 +167,12 @@ if (updateIgnoreFile) {
|
|||||||
|
|
||||||
const isIgnored = (linterName, path) => {
|
const isIgnored = (linterName, path) => {
|
||||||
return (
|
return (
|
||||||
iconIgnored[linterName] && iconIgnored[linterName].hasOwnProperty(path)
|
iconIgnored[linterName] && Object.hasOwn(iconIgnored[linterName], path)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ignoreIcon = (linterName, path, $) => {
|
const ignoreIcon = (linterName, path, $) => {
|
||||||
if (!iconIgnored[linterName]) {
|
iconIgnored[linterName] ||= {};
|
||||||
iconIgnored[linterName] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const title = $.find('title').text();
|
const title = $.find('title').text();
|
||||||
const iconName = htmlFriendlyToTitle(title);
|
const iconName = htmlFriendlyToTitle(title);
|
||||||
@ -173,7 +180,7 @@ const ignoreIcon = (linterName, path, $) => {
|
|||||||
iconIgnored[linterName][path] = iconName;
|
iconIgnored[linterName][path] = iconName;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export const config = {
|
||||||
rules: {
|
rules: {
|
||||||
elm: {
|
elm: {
|
||||||
svg: 1,
|
svg: 1,
|
||||||
@ -183,7 +190,7 @@ export default {
|
|||||||
},
|
},
|
||||||
attr: [
|
attr: [
|
||||||
{
|
{
|
||||||
// ensure that the SVG element has the appropriate attributes
|
// Ensure that the SVG element has the appropriate attributes
|
||||||
// alphabetically ordered
|
// alphabetically ordered
|
||||||
role: 'img',
|
role: 'img',
|
||||||
viewBox: `0 0 ${iconSize} ${iconSize}`,
|
viewBox: `0 0 ${iconSize} ${iconSize}`,
|
||||||
@ -193,12 +200,12 @@ export default {
|
|||||||
'rule::order': true,
|
'rule::order': true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// ensure that the title element has the appropriate attribute
|
// 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' attribute
|
// Ensure that the path element only has the 'd' attribute
|
||||||
// (no style, opacity, etc.)
|
// (no style, opacity, etc.)
|
||||||
d: SVG_PATH_REGEX,
|
d: SVG_PATH_REGEX,
|
||||||
'rule::selector': 'svg > path',
|
'rule::selector': 'svg > path',
|
||||||
@ -209,15 +216,15 @@ export default {
|
|||||||
(reporter, $, ast) => {
|
(reporter, $, ast) => {
|
||||||
reporter.name = 'icon-title';
|
reporter.name = 'icon-title';
|
||||||
|
|
||||||
const iconTitleText = $.find('title').text(),
|
const iconTitleText = $.find('title').text();
|
||||||
xmlNamedEntitiesCodepoints = [38, 60, 62],
|
const xmlNamedEntitiesCodepoints = [38, 60, 62];
|
||||||
xmlNamedEntities = ['amp', 'lt', 'gt'];
|
const xmlNamedEntities = ['amp', 'lt', 'gt'];
|
||||||
let _validCodepointsRepr = true;
|
let _validCodepointsRepr = true;
|
||||||
|
|
||||||
// avoid character codepoints as hexadecimal representation
|
// Avoid character codepoints as hexadecimal representation
|
||||||
const hexadecimalCodepoints = Array.from(
|
const hexadecimalCodepoints = [
|
||||||
iconTitleText.matchAll(/&#x([A-Fa-f0-9]+);/g),
|
...iconTitleText.matchAll(/&#x([A-Fa-f\d]+);/g),
|
||||||
);
|
];
|
||||||
if (hexadecimalCodepoints.length > 0) {
|
if (hexadecimalCodepoints.length > 0) {
|
||||||
_validCodepointsRepr = false;
|
_validCodepointsRepr = false;
|
||||||
|
|
||||||
@ -245,10 +252,10 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// avoid character codepoints as named entities
|
// Avoid character codepoints as named entities
|
||||||
const namedEntitiesCodepoints = Array.from(
|
const namedEntitiesCodepoints = [
|
||||||
iconTitleText.matchAll(/&([A-Za-z0-9]+);/g),
|
...iconTitleText.matchAll(/&([A-Za-z\d]+);/g),
|
||||||
);
|
];
|
||||||
if (namedEntitiesCodepoints.length > 0) {
|
if (namedEntitiesCodepoints.length > 0) {
|
||||||
for (const match of namedEntitiesCodepoints) {
|
for (const match of namedEntitiesCodepoints) {
|
||||||
const namedEntiyReprIndex =
|
const namedEntiyReprIndex =
|
||||||
@ -261,16 +268,15 @@ export default {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
namedEntityJsRepr === undefined ||
|
namedEntityJsRepr === undefined ||
|
||||||
namedEntityJsRepr.length != 1
|
namedEntityJsRepr.length !== 1
|
||||||
) {
|
) {
|
||||||
replacement = 'its decimal or literal representation';
|
replacement = 'its decimal or literal representation';
|
||||||
} else {
|
} else {
|
||||||
const namedEntityDec = namedEntityJsRepr.codePointAt(0);
|
const namedEntityDec = namedEntityJsRepr.codePointAt(0);
|
||||||
if (namedEntityDec < 128) {
|
replacement =
|
||||||
replacement = `"${namedEntityJsRepr}"`;
|
namedEntityDec < 128
|
||||||
} else {
|
? `"${namedEntityJsRepr}"`
|
||||||
replacement = `"&#${namedEntityDec};"`;
|
: `"&#${namedEntityDec};"`;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reporter.error(
|
reporter.error(
|
||||||
@ -283,11 +289,11 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_validCodepointsRepr) {
|
if (_validCodepointsRepr) {
|
||||||
// compare encoded title with original title and report error if not equal
|
// Compare encoded title with original title and report error if not equal
|
||||||
const encodingMatches = Array.from(
|
const encodingMatches = [
|
||||||
iconTitleText.matchAll(/&(#([0-9]+)|(amp|quot|lt|gt));/g),
|
...iconTitleText.matchAll(/&(#(\d+)|(amp|quot|lt|gt));/g),
|
||||||
),
|
];
|
||||||
encodedBuf = [];
|
const encodedBuf = [];
|
||||||
|
|
||||||
const indexesToIgnore = [];
|
const indexesToIgnore = [];
|
||||||
for (const match of encodingMatches) {
|
for (const match of encodingMatches) {
|
||||||
@ -300,8 +306,8 @@ export default {
|
|||||||
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)
|
||||||
let charDecimalCode = iconTitleText.charCodeAt(i);
|
const charDecimalCode = iconTitleText.codePointAt(i);
|
||||||
|
|
||||||
if (charDecimalCode > 127) {
|
if (charDecimalCode > 127) {
|
||||||
encodedBuf.unshift(`&#${charDecimalCode};`);
|
encodedBuf.unshift(`&#${charDecimalCode};`);
|
||||||
@ -318,6 +324,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const encodedIconTitleText = encodedBuf.join('');
|
const encodedIconTitleText = encodedBuf.join('');
|
||||||
if (encodedIconTitleText !== iconTitleText) {
|
if (encodedIconTitleText !== iconTitleText) {
|
||||||
_validCodepointsRepr = false;
|
_validCodepointsRepr = false;
|
||||||
@ -328,17 +335,20 @@ 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
|
||||||
|
// eslint-disable-next-line unicorn/prefer-number-properties
|
||||||
for (const match of encodingMatches.filter((m) => !isNaN(m[2]))) {
|
for (const match of encodingMatches.filter((m) => !isNaN(m[2]))) {
|
||||||
const decimalNumber = parseInt(match[2]);
|
const decimalNumber = Number.parseInt(match[2], 10);
|
||||||
if (decimalNumber > 127) {
|
if (decimalNumber > 127) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_validCodepointsRepr = false;
|
_validCodepointsRepr = false;
|
||||||
|
|
||||||
const decimalCodepointCharIndex =
|
const decimalCodepointCharIndex =
|
||||||
getTitleTextIndex(ast.source) + match.index + 1;
|
getTitleTextIndex(ast.source) + match.index + 1;
|
||||||
|
let replacement;
|
||||||
if (xmlNamedEntitiesCodepoints.includes(decimalNumber)) {
|
if (xmlNamedEntitiesCodepoints.includes(decimalNumber)) {
|
||||||
replacement = `"&${
|
replacement = `"&${
|
||||||
xmlNamedEntities[
|
xmlNamedEntities[
|
||||||
@ -347,7 +357,7 @@ export default {
|
|||||||
};"`;
|
};"`;
|
||||||
} else {
|
} else {
|
||||||
replacement = String.fromCodePoint(decimalNumber);
|
replacement = String.fromCodePoint(decimalNumber);
|
||||||
replacement = replacement == '"' ? `'"'` : `"${replacement}"`;
|
replacement = replacement === '"' ? `'"'` : `"${replacement}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
reporter.error(
|
reporter.error(
|
||||||
@ -379,8 +389,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [minX, minY, maxX, maxY] = getIconPathBbox(iconPath);
|
const [minX, minY, maxX, maxY] = getIconPathBbox(iconPath);
|
||||||
const width = +(maxX - minX).toFixed(iconFloatPrecision);
|
const width = Number((maxX - minX).toFixed(iconFloatPrecision));
|
||||||
const height = +(maxY - minY).toFixed(iconFloatPrecision);
|
const height = Number((maxY - minY).toFixed(iconFloatPrecision));
|
||||||
|
|
||||||
if (width === 0 && height === 0) {
|
if (width === 0 && height === 0) {
|
||||||
reporter.error(
|
reporter.error(
|
||||||
@ -407,24 +417,26 @@ export default {
|
|||||||
|
|
||||||
for (const segment of segments) {
|
for (const segment of segments) {
|
||||||
const precisionMax = Math.max(
|
const precisionMax = Math.max(
|
||||||
|
// eslint-disable-next-line unicorn/no-array-callback-reference
|
||||||
...segment.params.slice(1).map(countDecimals),
|
...segment.params.slice(1).map(countDecimals),
|
||||||
);
|
);
|
||||||
if (precisionMax > iconMaxFloatPrecision) {
|
if (precisionMax > iconMaxFloatPrecision) {
|
||||||
let errorMsg =
|
let errorMessage =
|
||||||
`found ${precisionMax} decimals in segment` +
|
`found ${precisionMax} decimals in segment` +
|
||||||
` "${iconPath.substring(segment.start, segment.end)}"`;
|
` "${iconPath.slice(segment.start, segment.end)}"`;
|
||||||
if (segment.chained) {
|
if (segment.chained) {
|
||||||
const readableChain = maybeShortenedWithEllipsis(
|
const readableChain = maybeShortenedWithEllipsis(
|
||||||
iconPath.substring(segment.chainStart, segment.chainEnd),
|
iconPath.slice(segment.chainStart, segment.chainEnd),
|
||||||
);
|
);
|
||||||
errorMsg += ` of chain "${readableChain}"`;
|
errorMessage += ` of chain "${readableChain}"`;
|
||||||
}
|
}
|
||||||
errorMsg += ` at index ${
|
|
||||||
|
errorMessage += ` at index ${
|
||||||
segment.start + getPathDIndex(ast.source)
|
segment.start + getPathDIndex(ast.source)
|
||||||
}`;
|
}`;
|
||||||
reporter.error(
|
reporter.error(
|
||||||
'Maximum precision should not be greater than' +
|
'Maximum precision should not be greater than' +
|
||||||
` ${iconMaxFloatPrecision}; ${errorMsg}`,
|
` ${iconMaxFloatPrecision}; ${errorMessage}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -446,10 +458,10 @@ export default {
|
|||||||
];
|
];
|
||||||
const upperMovementCommands = ['M', 'L'];
|
const upperMovementCommands = ['M', 'L'];
|
||||||
const upperHorDirectionCommand = 'H';
|
const upperHorDirectionCommand = 'H';
|
||||||
const upperVerDirectionCommand = 'V';
|
const upperVersionDirectionCommand = 'V';
|
||||||
const upperDirectionCommands = [
|
const upperDirectionCommands = [
|
||||||
upperHorDirectionCommand,
|
upperHorDirectionCommand,
|
||||||
upperVerDirectionCommand,
|
upperVersionDirectionCommand,
|
||||||
];
|
];
|
||||||
const upperCurveCommand = 'C';
|
const upperCurveCommand = 'C';
|
||||||
const upperShorthandCurveCommand = 'S';
|
const upperShorthandCurveCommand = 'S';
|
||||||
@ -458,23 +470,25 @@ export default {
|
|||||||
upperShorthandCurveCommand,
|
upperShorthandCurveCommand,
|
||||||
];
|
];
|
||||||
const curveCommands = [...lowerCurveCommands, ...upperCurveCommands];
|
const curveCommands = [...lowerCurveCommands, ...upperCurveCommands];
|
||||||
const commands = [
|
const commands = new Set([
|
||||||
...lowerMovementCommands,
|
...lowerMovementCommands,
|
||||||
...lowerDirectionCommands,
|
...lowerDirectionCommands,
|
||||||
...upperMovementCommands,
|
...upperMovementCommands,
|
||||||
...upperDirectionCommands,
|
...upperDirectionCommands,
|
||||||
...curveCommands,
|
...curveCommands,
|
||||||
];
|
]);
|
||||||
|
|
||||||
const isInvalidSegment = (
|
const isInvalidSegment = (
|
||||||
[command, x1Coord, y1Coord, ...rest],
|
[command, x1Coord, y1Coord, ...rest],
|
||||||
index,
|
index,
|
||||||
previousSegmentIsZ,
|
previousSegmentIsZ,
|
||||||
) => {
|
) => {
|
||||||
if (commands.includes(command)) {
|
if (commands.has(command)) {
|
||||||
// Relative directions (h or v) having a length of 0
|
// Relative directions (h or v) having a length of 0
|
||||||
if (lowerDirectionCommands.includes(command) && x1Coord === 0) {
|
if (lowerDirectionCommands.includes(command) && x1Coord === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relative movement (m or l) having a distance of 0
|
// Relative movement (m or l) having a distance of 0
|
||||||
if (
|
if (
|
||||||
index > 0 &&
|
index > 0 &&
|
||||||
@ -486,6 +500,7 @@ export default {
|
|||||||
// a relative placement (m) as if it were absolute (M)
|
// a relative placement (m) as if it were absolute (M)
|
||||||
return command.toLowerCase() === 'm' ? !previousSegmentIsZ : true;
|
return command.toLowerCase() === 'm' ? !previousSegmentIsZ : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
lowerCurveCommands.includes(command) &&
|
lowerCurveCommands.includes(command) &&
|
||||||
x1Coord === 0 &&
|
x1Coord === 0 &&
|
||||||
@ -503,45 +518,49 @@ export default {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
let [yPrevCoord, xPrevCoord] = [
|
let [yPreviousCoord, xPreviousCoord] = [
|
||||||
...absSegments[index - 1],
|
...absSegments[index - 1],
|
||||||
].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
|
||||||
if (upperDirectionCommands.includes(xPrevCoord)) {
|
if (upperDirectionCommands.includes(xPreviousCoord)) {
|
||||||
xPrevCoord = undefined;
|
xPreviousCoord = undefined;
|
||||||
yPrevCoord = undefined;
|
yPreviousCoord = undefined;
|
||||||
let idx = index;
|
let index_ = index;
|
||||||
while (
|
while (
|
||||||
--idx > 0 &&
|
--index_ > 0 &&
|
||||||
(xPrevCoord === undefined || yPrevCoord === undefined)
|
(xPreviousCoord === undefined || yPreviousCoord === undefined)
|
||||||
) {
|
) {
|
||||||
let [yPrevCoordDeep, xPrevCoordDeep] = [
|
let [yPreviousCoordDeep, xPreviousCoordDeep] = [
|
||||||
...absSegments[idx],
|
...absSegments[index_],
|
||||||
].reverse();
|
].reverse();
|
||||||
// If the previous command was a horizontal movement,
|
// If the previous command was a horizontal movement,
|
||||||
// we need to consider the single coordinate as x
|
// we need to consider the single coordinate as x
|
||||||
if (upperHorDirectionCommand === xPrevCoordDeep) {
|
if (upperHorDirectionCommand === xPreviousCoordDeep) {
|
||||||
xPrevCoordDeep = yPrevCoordDeep;
|
xPreviousCoordDeep = yPreviousCoordDeep;
|
||||||
yPrevCoordDeep = 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 (upperVerDirectionCommand === xPrevCoordDeep) {
|
if (upperVersionDirectionCommand === xPreviousCoordDeep) {
|
||||||
xPrevCoordDeep = undefined;
|
xPreviousCoordDeep = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
xPrevCoord === undefined &&
|
xPreviousCoord === undefined &&
|
||||||
xPrevCoordDeep !== undefined
|
xPreviousCoordDeep !== undefined
|
||||||
) {
|
) {
|
||||||
xPrevCoord = xPrevCoordDeep;
|
xPreviousCoord = xPreviousCoordDeep;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
yPrevCoord === undefined &&
|
yPreviousCoord === undefined &&
|
||||||
yPrevCoordDeep !== undefined
|
yPreviousCoordDeep !== undefined
|
||||||
) {
|
) {
|
||||||
yPrevCoord = yPrevCoordDeep;
|
yPreviousCoord = yPreviousCoordDeep;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -553,20 +572,21 @@ export default {
|
|||||||
// and a control point equal to the ending point
|
// and a control point equal to the ending point
|
||||||
if (
|
if (
|
||||||
upperShorthandCurveCommand === command &&
|
upperShorthandCurveCommand === command &&
|
||||||
x1Coord === xPrevCoord &&
|
x1Coord === xPreviousCoord &&
|
||||||
y1Coord === yPrevCoord &&
|
y1Coord === yPreviousCoord &&
|
||||||
x1Coord === x2Coord &&
|
x1Coord === x2Coord &&
|
||||||
y1Coord === y2Coord
|
y1Coord === y2Coord
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Absolute bézier curve (C) having
|
// Absolute bézier curve (C) having
|
||||||
// the same coordinate as the previous segment
|
// the same coordinate as the previous segment
|
||||||
// and last control point equal to the ending point
|
// and last control point equal to the ending point
|
||||||
if (
|
if (
|
||||||
upperCurveCommand === command &&
|
upperCurveCommand === command &&
|
||||||
x1Coord === xPrevCoord &&
|
x1Coord === xPreviousCoord &&
|
||||||
y1Coord === yPrevCoord &&
|
y1Coord === yPreviousCoord &&
|
||||||
x2Coord === xCoord &&
|
x2Coord === xCoord &&
|
||||||
y2Coord === yCoord
|
y2Coord === yCoord
|
||||||
) {
|
) {
|
||||||
@ -578,16 +598,16 @@ export default {
|
|||||||
// Absolute horizontal direction (H) having
|
// Absolute horizontal direction (H) having
|
||||||
// the same x coordinate as the previous segment
|
// the same x coordinate as the previous segment
|
||||||
(upperHorDirectionCommand === command &&
|
(upperHorDirectionCommand === command &&
|
||||||
x1Coord === xPrevCoord) ||
|
x1Coord === xPreviousCoord) ||
|
||||||
// Absolute vertical direction (V) having
|
// Absolute vertical direction (V) having
|
||||||
// the same y coordinate as the previous segment
|
// the same y coordinate as the previous segment
|
||||||
(upperVerDirectionCommand === command &&
|
(upperVersionDirectionCommand === command &&
|
||||||
x1Coord === yPrevCoord) ||
|
x1Coord === yPreviousCoord) ||
|
||||||
// Absolute movement (M or L) having the same
|
// Absolute movement (M or L) having the same
|
||||||
// coordinate as the previous segment
|
// coordinate as the previous segment
|
||||||
(upperMovementCommands.includes(command) &&
|
(upperMovementCommands.includes(command) &&
|
||||||
x1Coord === xPrevCoord &&
|
x1Coord === xPreviousCoord &&
|
||||||
y1Coord === yPrevCoord)
|
y1Coord === yPreviousCoord)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -599,13 +619,13 @@ export default {
|
|||||||
index > 0 && segments[index - 1].params[0].toLowerCase() === 'z';
|
index > 0 && segments[index - 1].params[0].toLowerCase() === 'z';
|
||||||
|
|
||||||
if (isInvalidSegment(segment.params, index, previousSegmentIsZ)) {
|
if (isInvalidSegment(segment.params, index, previousSegmentIsZ)) {
|
||||||
const [command, x1, y1, ...rest] = segment.params;
|
const [command, _x1, _y1, ...rest] = segment.params;
|
||||||
|
|
||||||
let errorMsg = `Ineffective segment "${iconPath.substring(
|
let errorMessage = `Ineffective segment "${iconPath.slice(
|
||||||
segment.start,
|
segment.start,
|
||||||
segment.end,
|
segment.end,
|
||||||
)}" found`,
|
)}" found`;
|
||||||
resolutionTip = 'should be removed';
|
let resolutionTip = 'should be removed';
|
||||||
|
|
||||||
if (curveCommands.includes(command)) {
|
if (curveCommands.includes(command)) {
|
||||||
const [x2, y2, x, y] = rest;
|
const [x2, y2, x, y] = rest;
|
||||||
@ -618,16 +638,19 @@ export default {
|
|||||||
x2,
|
x2,
|
||||||
)} ${removeLeadingZeros(y2)}" or removed`;
|
)} ${removeLeadingZeros(y2)}" or removed`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command === upperShorthandCurveCommand) {
|
if (command === upperShorthandCurveCommand) {
|
||||||
resolutionTip = `should be "L${removeLeadingZeros(
|
resolutionTip = `should be "L${removeLeadingZeros(
|
||||||
x2,
|
x2,
|
||||||
)} ${removeLeadingZeros(y2)}" or removed`;
|
)} ${removeLeadingZeros(y2)}" or removed`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command === lowerCurveCommand && (x !== 0 || y !== 0)) {
|
if (command === lowerCurveCommand && (x !== 0 || y !== 0)) {
|
||||||
resolutionTip = `should be "l${removeLeadingZeros(
|
resolutionTip = `should be "l${removeLeadingZeros(
|
||||||
x,
|
x,
|
||||||
)} ${removeLeadingZeros(y)}" or removed`;
|
)} ${removeLeadingZeros(y)}" or removed`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command === upperCurveCommand) {
|
if (command === upperCurveCommand) {
|
||||||
resolutionTip = `should be "L${removeLeadingZeros(
|
resolutionTip = `should be "L${removeLeadingZeros(
|
||||||
x,
|
x,
|
||||||
@ -637,15 +660,16 @@ export default {
|
|||||||
|
|
||||||
if (segment.chained) {
|
if (segment.chained) {
|
||||||
const readableChain = maybeShortenedWithEllipsis(
|
const readableChain = maybeShortenedWithEllipsis(
|
||||||
iconPath.substring(segment.chainStart, segment.chainEnd),
|
iconPath.slice(segment.chainStart, segment.chainEnd),
|
||||||
);
|
);
|
||||||
errorMsg += ` in chain "${readableChain}"`;
|
errorMessage += ` in chain "${readableChain}"`;
|
||||||
}
|
}
|
||||||
errorMsg += ` at index ${
|
|
||||||
|
errorMessage += ` at index ${
|
||||||
segment.start + getPathDIndex(ast.source)
|
segment.start + getPathDIndex(ast.source)
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
reporter.error(`${errorMsg} (${resolutionTip})`);
|
reporter.error(`${errorMessage} (${resolutionTip})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -657,192 +681,222 @@ export default {
|
|||||||
* (does not extracts collinear coordinates from curves).
|
* (does not extracts collinear coordinates from curves).
|
||||||
**/
|
**/
|
||||||
const getCollinearSegments = (iconPath) => {
|
const getCollinearSegments = (iconPath) => {
|
||||||
const segments = getIconPathSegments(iconPath),
|
const segments = getIconPathSegments(iconPath);
|
||||||
collinearSegments = [],
|
const collinearSegments = [];
|
||||||
straightLineCommands = 'HhVvLlMm';
|
const straightLineCommands = 'HhVvLlMm';
|
||||||
|
|
||||||
let currLine = [],
|
let currentLine = [];
|
||||||
currAbsCoord = [undefined, undefined],
|
let currentAbsCoord = [undefined, undefined];
|
||||||
startPoint,
|
let startPoint;
|
||||||
_inStraightLine = false,
|
let _inStraightLine = false;
|
||||||
_nextInStraightLine = false,
|
let _nextInStraightLine = false;
|
||||||
_resetStartPoint = false;
|
let _resetStartPoint = false;
|
||||||
|
|
||||||
for (let s = 0; s < segments.length; s++) {
|
for (let s = 0; s < segments.length; s++) {
|
||||||
const seg = segments[s],
|
const seg = segments[s];
|
||||||
parms = seg.params,
|
const parms = seg.params;
|
||||||
cmd = parms[0],
|
const cmd = parms[0];
|
||||||
nextCmd = s + 1 < segments.length ? segments[s + 1][0] : null;
|
const nextCmd = s + 1 < segments.length ? segments[s + 1][0] : null;
|
||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
// 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': {
|
||||||
currAbsCoord[0] = parms[1];
|
currentAbsCoord[0] = parms[1];
|
||||||
currAbsCoord[1] = parms[2];
|
currentAbsCoord[1] = parms[2];
|
||||||
// SVG 1.1:
|
// SVG 1.1:
|
||||||
// If a moveto is followed by multiple pairs of coordinates,
|
// If a moveto is followed by multiple pairs of coordinates,
|
||||||
// the subsequent pairs are treated as implicit lineto commands.
|
// the subsequent pairs are treated as implicit lineto commands.
|
||||||
if (!seg.chained || seg.chainStart === seg.start) {
|
if (!seg.chained || seg.chainStart === seg.start) {
|
||||||
startPoint = undefined;
|
startPoint = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'm':
|
}
|
||||||
currAbsCoord[0] =
|
|
||||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + parms[1];
|
case 'm': {
|
||||||
currAbsCoord[1] =
|
currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[1];
|
||||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + parms[2];
|
currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[2];
|
||||||
if (!seg.chained || seg.chainStart === seg.start) {
|
if (!seg.chained || seg.chainStart === seg.start) {
|
||||||
startPoint = undefined;
|
startPoint = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'H':
|
}
|
||||||
currAbsCoord[0] = parms[1];
|
|
||||||
|
case 'H': {
|
||||||
|
currentAbsCoord[0] = parms[1];
|
||||||
break;
|
break;
|
||||||
case 'h':
|
}
|
||||||
currAbsCoord[0] =
|
|
||||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + parms[1];
|
case 'h': {
|
||||||
|
currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[1];
|
||||||
break;
|
break;
|
||||||
case 'V':
|
}
|
||||||
currAbsCoord[1] = parms[1];
|
|
||||||
|
case 'V': {
|
||||||
|
currentAbsCoord[1] = parms[1];
|
||||||
break;
|
break;
|
||||||
case 'v':
|
}
|
||||||
currAbsCoord[1] =
|
|
||||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + parms[1];
|
case 'v': {
|
||||||
|
currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[1];
|
||||||
break;
|
break;
|
||||||
case 'L':
|
}
|
||||||
currAbsCoord[0] = parms[1];
|
|
||||||
currAbsCoord[1] = parms[2];
|
case 'L': {
|
||||||
|
currentAbsCoord[0] = parms[1];
|
||||||
|
currentAbsCoord[1] = parms[2];
|
||||||
break;
|
break;
|
||||||
case 'l':
|
}
|
||||||
currAbsCoord[0] =
|
|
||||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + parms[1];
|
case 'l': {
|
||||||
currAbsCoord[1] =
|
currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[1];
|
||||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + parms[2];
|
currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[2];
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'Z':
|
case 'Z':
|
||||||
case 'z':
|
case 'z': {
|
||||||
// TODO: Overlapping in Z should be handled in another rule
|
// TODO: Overlapping in Z should be handled in another rule
|
||||||
currAbsCoord = [startPoint[0], startPoint[1]];
|
currentAbsCoord = [startPoint[0], startPoint[1]];
|
||||||
_resetStartPoint = true;
|
_resetStartPoint = true;
|
||||||
break;
|
break;
|
||||||
case 'C':
|
}
|
||||||
currAbsCoord[0] = parms[5];
|
|
||||||
currAbsCoord[1] = parms[6];
|
case 'C': {
|
||||||
|
currentAbsCoord[0] = parms[5];
|
||||||
|
currentAbsCoord[1] = parms[6];
|
||||||
break;
|
break;
|
||||||
case 'c':
|
}
|
||||||
currAbsCoord[0] =
|
|
||||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + parms[5];
|
case 'c': {
|
||||||
currAbsCoord[1] =
|
currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[5];
|
||||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + parms[6];
|
currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[6];
|
||||||
break;
|
break;
|
||||||
case 'A':
|
}
|
||||||
currAbsCoord[0] = parms[6];
|
|
||||||
currAbsCoord[1] = parms[7];
|
case 'A': {
|
||||||
|
currentAbsCoord[0] = parms[6];
|
||||||
|
currentAbsCoord[1] = parms[7];
|
||||||
break;
|
break;
|
||||||
case 'a':
|
}
|
||||||
currAbsCoord[0] =
|
|
||||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + parms[6];
|
case 'a': {
|
||||||
currAbsCoord[1] =
|
currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[6];
|
||||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + parms[7];
|
currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[7];
|
||||||
break;
|
break;
|
||||||
case 's':
|
}
|
||||||
currAbsCoord[0] =
|
|
||||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + parms[1];
|
case 's': {
|
||||||
currAbsCoord[1] =
|
currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[1];
|
||||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + parms[2];
|
currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[2];
|
||||||
break;
|
break;
|
||||||
case 'S':
|
}
|
||||||
currAbsCoord[0] = parms[1];
|
|
||||||
currAbsCoord[1] = parms[2];
|
case 'S': {
|
||||||
|
currentAbsCoord[0] = parms[1];
|
||||||
|
currentAbsCoord[1] = parms[2];
|
||||||
break;
|
break;
|
||||||
case 't':
|
}
|
||||||
currAbsCoord[0] =
|
|
||||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + parms[1];
|
case 't': {
|
||||||
currAbsCoord[1] =
|
currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[1];
|
||||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + parms[2];
|
currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[2];
|
||||||
break;
|
break;
|
||||||
case 'T':
|
}
|
||||||
currAbsCoord[0] = parms[1];
|
|
||||||
currAbsCoord[1] = parms[2];
|
case 'T': {
|
||||||
|
currentAbsCoord[0] = parms[1];
|
||||||
|
currentAbsCoord[1] = parms[2];
|
||||||
break;
|
break;
|
||||||
case 'Q':
|
}
|
||||||
currAbsCoord[0] = parms[3];
|
|
||||||
currAbsCoord[1] = parms[4];
|
case 'Q': {
|
||||||
|
currentAbsCoord[0] = parms[3];
|
||||||
|
currentAbsCoord[1] = parms[4];
|
||||||
break;
|
break;
|
||||||
case 'q':
|
}
|
||||||
currAbsCoord[0] =
|
|
||||||
(!currAbsCoord[0] ? 0 : currAbsCoord[0]) + parms[3];
|
case 'q': {
|
||||||
currAbsCoord[1] =
|
currentAbsCoord[0] = (currentAbsCoord[0] || 0) + parms[3];
|
||||||
(!currAbsCoord[1] ? 0 : currAbsCoord[1]) + parms[4];
|
currentAbsCoord[1] = (currentAbsCoord[1] || 0) + parms[4];
|
||||||
break;
|
break;
|
||||||
default:
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
throw new Error(`"${cmd}" command not handled`);
|
throw new Error(`"${cmd}" command not handled`);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (startPoint === undefined) {
|
if (startPoint === undefined) {
|
||||||
startPoint = [currAbsCoord[0], currAbsCoord[1]];
|
startPoint = [currentAbsCoord[0], currentAbsCoord[1]];
|
||||||
} else if (_resetStartPoint) {
|
} else if (_resetStartPoint) {
|
||||||
startPoint = undefined;
|
startPoint = undefined;
|
||||||
_resetStartPoint = false;
|
_resetStartPoint = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_nextInStraightLine = straightLineCommands.includes(nextCmd);
|
_nextInStraightLine = straightLineCommands.includes(nextCmd);
|
||||||
let _exitingStraightLine = _inStraightLine && !_nextInStraightLine;
|
const _exitingStraightLine =
|
||||||
|
_inStraightLine && !_nextInStraightLine;
|
||||||
_inStraightLine = straightLineCommands.includes(cmd);
|
_inStraightLine = straightLineCommands.includes(cmd);
|
||||||
|
|
||||||
if (_inStraightLine) {
|
if (_inStraightLine) {
|
||||||
currLine.push([currAbsCoord[0], currAbsCoord[1]]);
|
currentLine.push([currentAbsCoord[0], currentAbsCoord[1]]);
|
||||||
} else {
|
} else {
|
||||||
if (_exitingStraightLine) {
|
if (_exitingStraightLine) {
|
||||||
if (straightLineCommands.includes(cmd)) {
|
if (straightLineCommands.includes(cmd)) {
|
||||||
currLine.push([currAbsCoord[0], currAbsCoord[1]]);
|
currentLine.push([currentAbsCoord[0], currentAbsCoord[1]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get collinear coordinates
|
// Get collinear coordinates
|
||||||
for (let p = 1; p < currLine.length - 1; p++) {
|
for (let p = 1; p < currentLine.length - 1; p++) {
|
||||||
let _collinearCoord = collinear(
|
const _collinearCoord = collinear(
|
||||||
currLine[p - 1][0],
|
currentLine[p - 1][0],
|
||||||
currLine[p - 1][1],
|
currentLine[p - 1][1],
|
||||||
currLine[p][0],
|
currentLine[p][0],
|
||||||
currLine[p][1],
|
currentLine[p][1],
|
||||||
currLine[p + 1][0],
|
currentLine[p + 1][0],
|
||||||
currLine[p + 1][1],
|
currentLine[p + 1][1],
|
||||||
);
|
);
|
||||||
if (_collinearCoord) {
|
if (_collinearCoord) {
|
||||||
collinearSegments.push(
|
collinearSegments.push(
|
||||||
segments[s - currLine.length + p + 1],
|
segments[s - currentLine.length + p + 1],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
currLine = [];
|
|
||||||
|
currentLine = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return collinearSegments;
|
return collinearSegments;
|
||||||
};
|
};
|
||||||
|
|
||||||
const iconPath = getIconPath($, filepath),
|
const iconPath = getIconPath($, filepath);
|
||||||
collinearSegments = getCollinearSegments(iconPath);
|
const collinearSegments = getCollinearSegments(iconPath);
|
||||||
if (collinearSegments.length === 0) {
|
if (collinearSegments.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathDIndex = getPathDIndex(ast.source);
|
const pathDIndex = getPathDIndex(ast.source);
|
||||||
for (const segment of collinearSegments) {
|
for (const segment of collinearSegments) {
|
||||||
let errorMsg = `Collinear segment "${iconPath.substring(
|
let errorMessage = `Collinear segment "${iconPath.slice(
|
||||||
segment.start,
|
segment.start,
|
||||||
segment.end,
|
segment.end,
|
||||||
)}" found`;
|
)}" found`;
|
||||||
if (segment.chained) {
|
if (segment.chained) {
|
||||||
let readableChain = maybeShortenedWithEllipsis(
|
const readableChain = maybeShortenedWithEllipsis(
|
||||||
iconPath.substring(segment.chainStart, segment.chainEnd),
|
iconPath.slice(segment.chainStart, segment.chainEnd),
|
||||||
);
|
);
|
||||||
errorMsg += ` in chain "${readableChain}"`;
|
errorMessage += ` in chain "${readableChain}"`;
|
||||||
}
|
}
|
||||||
errorMsg += ` at index ${
|
|
||||||
|
errorMessage += ` at index ${
|
||||||
segment.start + pathDIndex
|
segment.start + pathDIndex
|
||||||
} (should be removed)`;
|
} (should be removed)`;
|
||||||
reporter.error(errorMsg);
|
reporter.error(errorMessage);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(reporter, $, ast) => {
|
(reporter, $, ast) => {
|
||||||
@ -867,10 +921,8 @@ export default {
|
|||||||
const iconPath = getIconPath($, filepath);
|
const iconPath = getIconPath($, filepath);
|
||||||
|
|
||||||
// Find negative zeros inside path
|
// Find negative zeros inside path
|
||||||
const negativeZeroMatches = Array.from(
|
const negativeZeroMatches = [...iconPath.matchAll(negativeZerosRegexp)];
|
||||||
iconPath.matchAll(negativeZerosRegexp),
|
if (negativeZeroMatches.length > 0) {
|
||||||
);
|
|
||||||
if (negativeZeroMatches.length) {
|
|
||||||
// Calculate the index for each match in the file
|
// Calculate the index for each match in the file
|
||||||
const pathDIndex = getPathDIndex(ast.source);
|
const pathDIndex = getPathDIndex(ast.source);
|
||||||
|
|
||||||
@ -896,9 +948,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [minX, minY, maxX, maxY] = getIconPathBbox(iconPath);
|
const [minX, minY, maxX, maxY] = getIconPathBbox(iconPath);
|
||||||
const centerX = +((minX + maxX) / 2).toFixed(iconFloatPrecision);
|
const centerX = Number(((minX + maxX) / 2).toFixed(iconFloatPrecision));
|
||||||
const devianceX = centerX - iconTargetCenter;
|
const devianceX = centerX - iconTargetCenter;
|
||||||
const centerY = +((minY + maxY) / 2).toFixed(iconFloatPrecision);
|
const centerY = Number(((minY + maxY) / 2).toFixed(iconFloatPrecision));
|
||||||
const devianceY = centerY - iconTargetCenter;
|
const devianceY = centerY - iconTargetCenter;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -920,38 +972,38 @@ export default {
|
|||||||
const iconPath = getIconPath($, filepath);
|
const iconPath = getIconPath($, filepath);
|
||||||
|
|
||||||
if (!SVG_PATH_REGEX.test(iconPath)) {
|
if (!SVG_PATH_REGEX.test(iconPath)) {
|
||||||
let errorMsg = 'Invalid path format',
|
const errorMessage = 'Invalid path format';
|
||||||
reason;
|
let reason;
|
||||||
|
|
||||||
if (!iconPath.startsWith('M') && !iconPath.startsWith('m')) {
|
if (!iconPath.startsWith('M') && !iconPath.startsWith('m')) {
|
||||||
// doesn't start with moveto
|
// Doesn't start with moveto
|
||||||
reason =
|
reason =
|
||||||
'should start with "moveto" command ("M" or "m"),' +
|
'should start with "moveto" command ("M" or "m"),' +
|
||||||
` but starts with \"${iconPath[0]}\"`;
|
` but starts with "${iconPath[0]}"`;
|
||||||
reporter.error(`${errorMsg}: ${reason}`);
|
reporter.error(`${errorMessage}: ${reason}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const validPathCharacters = SVG_PATH_REGEX.source.replace(
|
const validPathCharacters = SVG_PATH_REGEX.source.replaceAll(
|
||||||
/[\[\]+^$]/g,
|
/[[\]+^$]/g,
|
||||||
'',
|
'',
|
||||||
),
|
);
|
||||||
invalidCharactersMsgs = [],
|
const invalidCharactersMsgs = [];
|
||||||
pathDIndex = getPathDIndex(ast.source);
|
const pathDIndex = getPathDIndex(ast.source);
|
||||||
|
|
||||||
for (let [i, char] of Object.entries(iconPath)) {
|
for (const [i, char] of Object.entries(iconPath)) {
|
||||||
if (validPathCharacters.indexOf(char) === -1) {
|
if (!validPathCharacters.includes(char)) {
|
||||||
invalidCharactersMsgs.push(
|
invalidCharactersMsgs.push(
|
||||||
`"${char}" at index ${pathDIndex + parseInt(i)}`,
|
`"${char}" at index ${pathDIndex + Number.parseInt(i, 10)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// contains invalid characters
|
// Contains invalid characters
|
||||||
if (invalidCharactersMsgs.length > 0) {
|
if (invalidCharactersMsgs.length > 0) {
|
||||||
reason = `unexpected character${
|
reason = `unexpected character${
|
||||||
invalidCharactersMsgs.length > 1 ? 's' : ''
|
invalidCharactersMsgs.length > 1 ? 's' : ''
|
||||||
} found (${invalidCharactersMsgs.join(', ')})`;
|
} found (${invalidCharactersMsgs.join(', ')})`;
|
||||||
reporter.error(`${errorMsg}: ${reason}`);
|
reporter.error(`${errorMessage}: ${reason}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
@ -1,4 +1,4 @@
|
|||||||
export default {
|
const config = {
|
||||||
multipass: true,
|
multipass: true,
|
||||||
eol: 'lf',
|
eol: 'lf',
|
||||||
plugins: [
|
plugins: [
|
||||||
@ -62,7 +62,7 @@ export default {
|
|||||||
// Convert basic shapes (such as <circle>) to <path>
|
// Convert basic shapes (such as <circle>) to <path>
|
||||||
name: 'convertShapeToPath',
|
name: 'convertShapeToPath',
|
||||||
params: {
|
params: {
|
||||||
// including <arc>
|
// Including <arc>
|
||||||
convertArcs: true,
|
convertArcs: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -102,3 +102,5 @@ export default {
|
|||||||
'reusePaths',
|
'reusePaths',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { test } from 'mocha';
|
|
||||||
import {strict as assert} from 'node:assert';
|
import {strict as assert} from 'node:assert';
|
||||||
|
import {test} from 'mocha';
|
||||||
import {getThirdPartyExtensions} from '../sdk.mjs';
|
import {getThirdPartyExtensions} from '../sdk.mjs';
|
||||||
|
|
||||||
test('README third party extensions must be alphabetically sorted', async () => {
|
test('README third party extensions must be alphabetically sorted', async () => {
|
||||||
@ -7,10 +7,10 @@ test('README third party extensions must be alphabetically sorted', async () =>
|
|||||||
assert.ok(thirdPartyExtensions.length > 0);
|
assert.ok(thirdPartyExtensions.length > 0);
|
||||||
|
|
||||||
const thirdPartyExtensionsNames = thirdPartyExtensions.map(
|
const thirdPartyExtensionsNames = thirdPartyExtensions.map(
|
||||||
(ext) => ext.module.name,
|
(extension) => extension.module.name,
|
||||||
);
|
);
|
||||||
|
|
||||||
const expectedOrder = thirdPartyExtensionsNames.slice().sort();
|
const expectedOrder = [...thirdPartyExtensionsNames].sort();
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
thirdPartyExtensionsNames,
|
thirdPartyExtensionsNames,
|
||||||
expectedOrder,
|
expectedOrder,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { getIconsData, getIconSlug, slugToVariableName } from '../sdk.mjs';
|
|
||||||
import * as simpleIcons from '../index.mjs';
|
import * as simpleIcons from '../index.mjs';
|
||||||
|
import {getIconSlug, getIconsData, slugToVariableName} from '../sdk.mjs';
|
||||||
import {testIcon} from './test-icon.js';
|
import {testIcon} from './test-icon.js';
|
||||||
|
|
||||||
for (const icon of await getIconsData()) {
|
for (const icon of await getIconsData()) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import {strict as assert} from 'node:assert';
|
||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { strict as assert } from 'node:assert';
|
|
||||||
import {describe, it} from 'mocha';
|
import {describe, it} from 'mocha';
|
||||||
import {
|
import {
|
||||||
SVG_PATH_REGEX,
|
SVG_PATH_REGEX,
|
||||||
@ -9,7 +9,7 @@ import {
|
|||||||
titleToSlug,
|
titleToSlug,
|
||||||
} from '../sdk.mjs';
|
} from '../sdk.mjs';
|
||||||
|
|
||||||
const iconsDir = path.resolve(
|
const iconsDirectory = path.resolve(
|
||||||
getDirnameFromImportMeta(import.meta.url),
|
getDirnameFromImportMeta(import.meta.url),
|
||||||
'..',
|
'..',
|
||||||
'icons',
|
'icons',
|
||||||
@ -26,7 +26,7 @@ const iconsDir = path.resolve(
|
|||||||
* @param {String} slug Icon data slug
|
* @param {String} slug Icon data slug
|
||||||
*/
|
*/
|
||||||
export const testIcon = (icon, subject, slug) => {
|
export const testIcon = (icon, subject, slug) => {
|
||||||
const svgPath = path.resolve(iconsDir, `${slug}.svg`);
|
const svgPath = path.resolve(iconsDirectory, `${slug}.svg`);
|
||||||
|
|
||||||
describe(icon.title, () => {
|
describe(icon.title, () => {
|
||||||
it('has the correct "title"', () => {
|
it('has the correct "title"', () => {
|
||||||
@ -81,7 +81,7 @@ export const testIcon = (icon, subject, slug) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (icon.slug) {
|
if (icon.slug) {
|
||||||
// if an icon data has a slug, it must be different to the
|
// If an icon data has a slug, it must be different to the
|
||||||
// slug inferred from the title, which prevents adding
|
// slug inferred from the title, which prevents adding
|
||||||
// unnecessary slugs to icons data
|
// unnecessary slugs to icons data
|
||||||
it(`'${icon.title}' slug must be necessary`, () => {
|
it(`'${icon.title}' slug must be necessary`, () => {
|
||||||
|
5
types.d.ts
vendored
5
types.d.ts
vendored
@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
export type License = SPDXLicense | CustomLicense;
|
export type License = SPDXLicense | CustomLicense;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
export type SPDXLicense = {
|
export type SPDXLicense = {
|
||||||
type: string;
|
type: string;
|
||||||
url: string;
|
url: string;
|
||||||
@ -18,7 +19,7 @@ export type CustomLicense = {
|
|||||||
/**
|
/**
|
||||||
* The data for a Simple Icon as is exported by the npm package.
|
* The data for a Simple Icon as is exported by the npm package.
|
||||||
*/
|
*/
|
||||||
export interface SimpleIcon {
|
export type SimpleIcon = {
|
||||||
title: string;
|
title: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
svg: string;
|
svg: string;
|
||||||
@ -27,4 +28,4 @@ export interface SimpleIcon {
|
|||||||
hex: string;
|
hex: string;
|
||||||
guidelines?: string;
|
guidelines?: string;
|
||||||
license?: License;
|
license?: License;
|
||||||
}
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user