mirror of
https://github.com/svg/svgo.git
synced 2025-08-01 18:46:52 +03:00
fix: improve jsdoc types and remove excludes (#2107)
This commit is contained in:
@ -14,15 +14,15 @@
|
||||
import SAX from 'sax';
|
||||
import { textElems } from '../plugins/_collections.js';
|
||||
|
||||
class SvgoParserError extends Error {
|
||||
export class SvgoParserError extends Error {
|
||||
/**
|
||||
* @param message {string}
|
||||
* @param line {number}
|
||||
* @param column {number}
|
||||
* @param source {string}
|
||||
* @param file {void | string}
|
||||
* @param {string} message
|
||||
* @param {number} line
|
||||
* @param {number} column
|
||||
* @param {string} source
|
||||
* @param {string|undefined} file
|
||||
*/
|
||||
constructor(message, line, column, source, file) {
|
||||
constructor(message, line, column, source, file = undefined) {
|
||||
super(message);
|
||||
this.name = 'SvgoParserError';
|
||||
this.message = `${file || '<input>'}:${line}:${column}: ${message}`;
|
||||
@ -34,6 +34,7 @@ class SvgoParserError extends Error {
|
||||
Error.captureStackTrace(this, SvgoParserError);
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
const lines = this.source.split(/\r?\n/);
|
||||
const startLine = Math.max(this.line - 3, 0);
|
||||
|
@ -67,12 +67,10 @@ const entities = {
|
||||
/**
|
||||
* convert XAST to SVG string
|
||||
*
|
||||
* @type {(data: XastRoot, config: StringifyOptions) => string}
|
||||
* @type {(data: XastRoot, userOptions?: StringifyOptions) => string}
|
||||
*/
|
||||
export const stringifySvg = (data, userOptions = {}) => {
|
||||
/**
|
||||
* @type {Options}
|
||||
*/
|
||||
/** @type {Options} */
|
||||
const config = { ...defaults, ...userOptions };
|
||||
const indent = config.indent;
|
||||
let newIndent = ' ';
|
||||
@ -81,9 +79,7 @@ export const stringifySvg = (data, userOptions = {}) => {
|
||||
} else if (typeof indent === 'string') {
|
||||
newIndent = indent;
|
||||
}
|
||||
/**
|
||||
* @type {State}
|
||||
*/
|
||||
/** @type {State} */
|
||||
const state = {
|
||||
indent: newIndent,
|
||||
textContext: null,
|
||||
|
4
lib/svgo-node.d.ts
vendored
4
lib/svgo-node.d.ts
vendored
@ -1,6 +1,6 @@
|
||||
import { Config } from './svgo';
|
||||
import type { Config } from './svgo.js';
|
||||
|
||||
export * from './svgo';
|
||||
export * from './svgo.js';
|
||||
|
||||
/**
|
||||
* If you write a tool on top of svgo you might need a way to load svgo config.
|
||||
|
@ -1,6 +1,5 @@
|
||||
import os from 'os';
|
||||
import fs from 'fs';
|
||||
import { pathToFileURL } from 'url';
|
||||
import path from 'path';
|
||||
import {
|
||||
VERSION,
|
||||
@ -11,10 +10,17 @@ import {
|
||||
_collections,
|
||||
} from './svgo.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('./svgo.js').Config} Config
|
||||
* @typedef {import('./svgo.js').Output} Output
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} configFile
|
||||
* @returns {Promise<Config>}
|
||||
*/
|
||||
const importConfig = async (configFile) => {
|
||||
// dynamic import expects file url instead of path and may fail
|
||||
// when windows path is provided
|
||||
const imported = await import(pathToFileURL(configFile));
|
||||
const imported = await import(path.resolve(configFile));
|
||||
const config = imported.default;
|
||||
|
||||
if (config == null || typeof config !== 'object' || Array.isArray(config)) {
|
||||
@ -23,6 +29,10 @@ const importConfig = async (configFile) => {
|
||||
return config;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
const isFile = async (file) => {
|
||||
try {
|
||||
const stats = await fs.promises.stat(file);
|
||||
@ -40,6 +50,11 @@ export {
|
||||
_collections,
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} configFile
|
||||
* @param {string} cwd
|
||||
* @returns {Promise<?Config>}
|
||||
*/
|
||||
export const loadConfig = async (configFile, cwd = process.cwd()) => {
|
||||
if (configFile != null) {
|
||||
if (path.isAbsolute(configFile)) {
|
||||
@ -71,6 +86,11 @@ export const loadConfig = async (configFile, cwd = process.cwd()) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Config} config
|
||||
* @returns {Output}
|
||||
*/
|
||||
export const optimize = (input, config) => {
|
||||
if (config == null) {
|
||||
config = {};
|
||||
|
@ -3,7 +3,7 @@ import path from 'path';
|
||||
import { optimize, loadConfig } from './svgo-node.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../lib/types.js').Plugin} Plugin
|
||||
* @typedef {import('../lib/types.js').Plugin<?>} Plugin
|
||||
*/
|
||||
|
||||
const describeLF = os.EOL === '\r\n' ? describe.skip : describe;
|
||||
|
2
lib/svgo.d.ts
vendored
2
lib/svgo.d.ts
vendored
@ -33,7 +33,7 @@ export type PluginConfig =
|
||||
}[keyof BuiltinsWithRequiredParams]
|
||||
| CustomPlugin;
|
||||
|
||||
type BuiltinPlugin<Name, Params> = {
|
||||
export type BuiltinPlugin<Name, Params> = {
|
||||
/** Name of the plugin, also known as the plugin ID. */
|
||||
name: Name;
|
||||
description?: string;
|
||||
|
15
lib/svgo.js
15
lib/svgo.js
@ -9,6 +9,9 @@ import _collections from '../plugins/_collections.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('./svgo.js').BuiltinPluginOrPreset<?, ?>} BuiltinPluginOrPreset
|
||||
* @typedef {import('./svgo.js').Config} Config
|
||||
* @typedef {import('./svgo.js').Output} Output
|
||||
* @typedef {import('./svgo.js').PluginConfig} PluginConfig
|
||||
*/
|
||||
|
||||
const pluginsMap = new Map();
|
||||
@ -31,6 +34,10 @@ function getPlugin(name) {
|
||||
return pluginsMap.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|PluginConfig} plugin
|
||||
* @returns {?PluginConfig}
|
||||
*/
|
||||
const resolvePluginConfig = (plugin) => {
|
||||
if (typeof plugin === 'string') {
|
||||
// resolve builtin plugin specified as string
|
||||
@ -49,6 +56,7 @@ const resolvePluginConfig = (plugin) => {
|
||||
throw Error(`Plugin name must be specified`);
|
||||
}
|
||||
// use custom plugin implementation
|
||||
// @ts-expect-error Checking for CustomPlugin with the presence of fn
|
||||
let fn = plugin.fn;
|
||||
if (fn == null) {
|
||||
// resolve builtin plugin implementation
|
||||
@ -75,6 +83,11 @@ export {
|
||||
_collections,
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Config} config
|
||||
* @returns {Output}
|
||||
*/
|
||||
export const optimize = (input, config) => {
|
||||
if (config == null) {
|
||||
config = {};
|
||||
@ -107,6 +120,8 @@ export const optimize = (input, config) => {
|
||||
'Warning: plugins list includes null or undefined elements, these will be ignored.',
|
||||
);
|
||||
}
|
||||
|
||||
/** @type {Config} */
|
||||
const globalOverrides = {};
|
||||
if (config.floatPrecision != null) {
|
||||
globalOverrides.floatPrecision = config.floatPrecision;
|
||||
|
@ -1,5 +1,10 @@
|
||||
import { jest } from '@jest/globals';
|
||||
import { optimize } from './svgo.js';
|
||||
import { SvgoParserError } from './parser.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('./svgo.js').CustomPlugin} CustomPlugin
|
||||
*/
|
||||
|
||||
test('allow to setup default preset', () => {
|
||||
const svg = `
|
||||
@ -65,6 +70,7 @@ test('warn when user tries enable plugins in preset', () => {
|
||||
const warn = jest.spyOn(console, 'warn');
|
||||
optimize(svg, {
|
||||
plugins: [
|
||||
// @ts-expect-error Testing if we receive config that diverges from type definitions.
|
||||
{
|
||||
name: 'preset-default',
|
||||
params: {
|
||||
@ -138,6 +144,7 @@ describe('allow to configure EOL', () => {
|
||||
</svg>
|
||||
`;
|
||||
const { data } = optimize(svg, {
|
||||
// @ts-expect-error Testing if we receive config that diverges from type definitions.
|
||||
js2svg: { eol: 'invalid', pretty: true, indent: 2 },
|
||||
});
|
||||
expect(data).toBe(
|
||||
@ -256,32 +263,35 @@ test('plugin precision should override preset precision', () => {
|
||||
});
|
||||
|
||||
test('provides informative error in result', () => {
|
||||
expect.assertions(6);
|
||||
const svg = `<svg viewBox="0 0 120 120">
|
||||
<circle fill="#ff0000" cx=60.444444" cy="60" r="50"/>
|
||||
</svg>
|
||||
`;
|
||||
try {
|
||||
optimize(svg, { path: 'test.svg' });
|
||||
} catch (error) {
|
||||
const error = new SvgoParserError(
|
||||
'Unquoted attribute value',
|
||||
2,
|
||||
33,
|
||||
svg,
|
||||
'test.svg',
|
||||
);
|
||||
expect(() => optimize(svg, { path: 'test.svg' })).toThrow(error);
|
||||
expect(error.name).toBe('SvgoParserError');
|
||||
expect(error.message).toBe('test.svg:2:33: Unquoted attribute value');
|
||||
expect(error.reason).toBe('Unquoted attribute value');
|
||||
expect(error.line).toBe(2);
|
||||
expect(error.column).toBe(33);
|
||||
expect(error.source).toBe(svg);
|
||||
}
|
||||
});
|
||||
|
||||
test('provides code snippet in rendered error', () => {
|
||||
expect.assertions(1);
|
||||
const svg = `<svg viewBox="0 0 120 120">
|
||||
<circle fill="#ff0000" cx=60.444444" cy="60" r="50"/>
|
||||
</svg>
|
||||
`;
|
||||
try {
|
||||
optimize(svg, { path: 'test.svg' });
|
||||
} catch (error) {
|
||||
const error = new SvgoParserError(
|
||||
'Unquoted attribute value',
|
||||
2,
|
||||
29,
|
||||
svg,
|
||||
'test.svg',
|
||||
);
|
||||
expect(() => optimize(svg, { path: 'test.svg' })).toThrow(error);
|
||||
expect(error.toString())
|
||||
.toBe(`SvgoParserError: test.svg:2:29: Unquoted attribute value
|
||||
|
||||
@ -291,11 +301,9 @@ test('provides code snippet in rendered error', () => {
|
||||
3 | </svg>
|
||||
4 |
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
test('supports errors without path', () => {
|
||||
expect.assertions(1);
|
||||
const svg = `<svg viewBox="0 0 120 120">
|
||||
<circle/>
|
||||
<circle/>
|
||||
@ -309,9 +317,8 @@ test('supports errors without path', () => {
|
||||
<circle fill="#ff0000" cx=60.444444" cy="60" r="50"/>
|
||||
</svg>
|
||||
`;
|
||||
try {
|
||||
optimize(svg);
|
||||
} catch (error) {
|
||||
const error = new SvgoParserError('Unquoted attribute value', 11, 29, svg);
|
||||
expect(() => optimize(svg)).toThrow(error);
|
||||
expect(error.toString())
|
||||
.toBe(`SvgoParserError: <input>:11:29: Unquoted attribute value
|
||||
|
||||
@ -322,18 +329,16 @@ test('supports errors without path', () => {
|
||||
12 | </svg>
|
||||
13 |
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
test('slices long line in error code snippet', () => {
|
||||
expect.assertions(1);
|
||||
const svg = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" viewBox="0 0 230 120">
|
||||
<path d="M318.198 551.135 530.33 918.56l-289.778-77.646 38.823-144.889c77.646-289.778 294.98-231.543 256.156-86.655s178.51 203.124 217.334 58.235q58.234-217.334 250.955 222.534t579.555 155.292z stroke-width="1.5" fill="red" stroke="red" />
|
||||
</svg>
|
||||
`;
|
||||
try {
|
||||
optimize(svg);
|
||||
} catch (error) {
|
||||
const error = new SvgoParserError('Invalid attribute name', 2, 211, svg);
|
||||
|
||||
expect(() => optimize(svg)).toThrow(error);
|
||||
expect(error.toString())
|
||||
.toBe(`SvgoParserError: <input>:2:211: Invalid attribute name
|
||||
|
||||
@ -343,12 +348,13 @@ test('slices long line in error code snippet', () => {
|
||||
3 |
|
||||
4 |
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
test('multipass option should trigger plugins multiple times', () => {
|
||||
const svg = `<svg id="abcdefghijklmnopqrstuvwxyz"></svg>`;
|
||||
/** @type {number[]} */
|
||||
const list = [];
|
||||
/** @type {CustomPlugin} */
|
||||
const testPlugin = {
|
||||
name: 'testPlugin',
|
||||
fn: (_root, _params, info) => {
|
||||
|
116
lib/svgo/coa.js
116
lib/svgo/coa.js
@ -5,6 +5,7 @@ import { fileURLToPath } from 'url';
|
||||
import { encodeSVGDatauri, decodeSVGDatauri } from './tools.js';
|
||||
import { loadConfig, optimize } from '../svgo-node.js';
|
||||
import { builtin } from '../builtin.js';
|
||||
import { SvgoParserError } from '../parser.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('commander').Command} Command
|
||||
@ -14,8 +15,6 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const pkgPath = path.join(__dirname, '../../package.json');
|
||||
const PKG = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
||||
|
||||
const regSVGFile = /\.svg$/i;
|
||||
|
||||
/**
|
||||
* Synchronously check if path is a directory. Tolerant to errors like ENOENT.
|
||||
*
|
||||
@ -89,22 +88,17 @@ export default function makeProgram(program) {
|
||||
.action(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} args
|
||||
* @param {any} opts
|
||||
* @param {Command} command
|
||||
* @returns
|
||||
*/
|
||||
async function action(args, opts, command) {
|
||||
var input = opts.input || args;
|
||||
var output = opts.output;
|
||||
var config = {};
|
||||
|
||||
if (opts.precision != null) {
|
||||
const number = Number.parseInt(opts.precision, 10);
|
||||
if (Number.isNaN(number)) {
|
||||
console.error(
|
||||
"error: option '-p, --precision' argument must be an integer number",
|
||||
);
|
||||
process.exit(1);
|
||||
} else {
|
||||
opts.precision = number;
|
||||
}
|
||||
}
|
||||
const input = opts.input || args;
|
||||
let output = opts.output;
|
||||
/** @type {any} */
|
||||
let config = {};
|
||||
|
||||
if (opts.datauri != null) {
|
||||
if (
|
||||
@ -155,13 +149,8 @@ async function action(args, opts, command) {
|
||||
return command.help();
|
||||
}
|
||||
|
||||
if (
|
||||
typeof process == 'object' &&
|
||||
process.versions &&
|
||||
process.versions.node &&
|
||||
PKG &&
|
||||
PKG.engines.node
|
||||
) {
|
||||
if (process?.versions?.node && PKG.engines.node) {
|
||||
// @ts-expect-error We control this and ensure it is never null.
|
||||
var nodeVersion = String(PKG.engines.node).match(/\d*(\.\d+)*/)[0];
|
||||
if (parseFloat(process.versions.node) < parseFloat(nodeVersion)) {
|
||||
throw Error(
|
||||
@ -188,13 +177,20 @@ async function action(args, opts, command) {
|
||||
|
||||
// --exclude
|
||||
config.exclude = opts.exclude
|
||||
? opts.exclude.map((pattern) => RegExp(pattern))
|
||||
? opts.exclude.map((/** @type {string} */ pattern) => RegExp(pattern))
|
||||
: [];
|
||||
|
||||
// --precision
|
||||
if (opts.precision != null) {
|
||||
var precision = Math.min(Math.max(0, opts.precision), 20);
|
||||
config.floatPrecision = precision;
|
||||
const number = Number.parseInt(opts.precision, 10);
|
||||
if (Number.isNaN(number)) {
|
||||
console.error(
|
||||
"error: option '-p, --precision' argument must be an integer number",
|
||||
);
|
||||
process.exit(1);
|
||||
} else {
|
||||
config.floatPrecision = Math.min(Math.max(0, number), 20);
|
||||
}
|
||||
}
|
||||
|
||||
// --multipass
|
||||
@ -227,7 +223,7 @@ async function action(args, opts, command) {
|
||||
if (output) {
|
||||
if (input.length && input[0] != '-') {
|
||||
if (output.length == 1 && checkIsDir(output[0])) {
|
||||
var dir = output[0];
|
||||
const dir = output[0];
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
output[i] = checkIsDir(input[i])
|
||||
? input[i]
|
||||
@ -270,7 +266,9 @@ async function action(args, opts, command) {
|
||||
// file
|
||||
} else {
|
||||
await Promise.all(
|
||||
input.map((file, n) => optimizeFile(config, file, output[n])),
|
||||
input.map((/** @type {string} */ file, /** @type {number} */ n) =>
|
||||
optimizeFile(config, file, output[n]),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -285,10 +283,10 @@ async function action(args, opts, command) {
|
||||
/**
|
||||
* Optimize SVG files in a directory.
|
||||
*
|
||||
* @param {Object} config options
|
||||
* @param {any} config options
|
||||
* @param {string} dir input directory
|
||||
* @param {string} output output directory
|
||||
* @return {Promise}
|
||||
* @return {Promise<any>}
|
||||
*/
|
||||
function optimizeFolder(config, dir, output) {
|
||||
if (!config.quiet) {
|
||||
@ -302,11 +300,11 @@ function optimizeFolder(config, dir, output) {
|
||||
/**
|
||||
* Process given files, take only SVG.
|
||||
*
|
||||
* @param {Object} config options
|
||||
* @param {any} config options
|
||||
* @param {string} dir input directory
|
||||
* @param {Array} files list of file names in the directory
|
||||
* @param {string[]} files list of file names in the directory
|
||||
* @param {string} output output directory
|
||||
* @return {Promise}
|
||||
* @return {Promise<any>}
|
||||
*/
|
||||
function processDirectory(config, dir, files, output) {
|
||||
// take only *.svg files, recursively if necessary
|
||||
@ -330,27 +328,31 @@ function processDirectory(config, dir, files, output) {
|
||||
/**
|
||||
* Get SVG files descriptions.
|
||||
*
|
||||
* @param {Object} config options
|
||||
* @param {any} config options
|
||||
* @param {string} dir input directory
|
||||
* @param {Array} files list of file names in the directory
|
||||
* @param {string[]} files list of file names in the directory
|
||||
* @param {string} output output directory
|
||||
* @return {Array}
|
||||
* @return {any[]}
|
||||
*/
|
||||
function getFilesDescriptions(config, dir, files, output) {
|
||||
const filesInThisFolder = files
|
||||
.filter(
|
||||
(name) =>
|
||||
regSVGFile.test(name) &&
|
||||
!config.exclude.some((regExclude) => regExclude.test(name)),
|
||||
name.slice(-4).toLowerCase() === '.svg' &&
|
||||
!config.exclude.some((/** @type {RegExp} */ regExclude) =>
|
||||
regExclude.test(name),
|
||||
),
|
||||
)
|
||||
.map((name) => ({
|
||||
inputPath: path.resolve(dir, name),
|
||||
outputPath: path.resolve(output, name),
|
||||
}));
|
||||
|
||||
return config.recursive
|
||||
? [].concat(
|
||||
filesInThisFolder,
|
||||
if (!config.recursive) {
|
||||
return filesInThisFolder;
|
||||
}
|
||||
|
||||
return filesInThisFolder.concat(
|
||||
files
|
||||
.filter((name) => checkIsDir(path.resolve(dir, name)))
|
||||
.map((subFolderName) => {
|
||||
@ -364,9 +366,8 @@ function getFilesDescriptions(config, dir, files, output) {
|
||||
subFolderOutput,
|
||||
);
|
||||
})
|
||||
.reduce((a, b) => [].concat(a, b), []),
|
||||
)
|
||||
: filesInThisFolder;
|
||||
.reduce((a, b) => a.concat(b), []),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -375,7 +376,7 @@ function getFilesDescriptions(config, dir, files, output) {
|
||||
* @param {Object} config options
|
||||
* @param {string} file
|
||||
* @param {string} output
|
||||
* @return {Promise}
|
||||
* @return {Promise<any>}
|
||||
*/
|
||||
function optimizeFile(config, file, output) {
|
||||
return fs.promises.readFile(file, 'utf8').then(
|
||||
@ -387,13 +388,14 @@ function optimizeFile(config, file, output) {
|
||||
/**
|
||||
* Optimize SVG data.
|
||||
*
|
||||
* @param {Object} config options
|
||||
* @param {any} config options
|
||||
* @param {?{ path: string }} info
|
||||
* @param {string} data SVG content to optimize
|
||||
* @param {string} output where to write optimized file
|
||||
* @param {string} [input] input file name (being used if output is a directory)
|
||||
* @return {Promise}
|
||||
* @param {any} input input file name (being used if output is a directory)
|
||||
* @return {Promise<any>}
|
||||
*/
|
||||
function processSVGData(config, info, data, output, input) {
|
||||
function processSVGData(config, info, data, output, input = undefined) {
|
||||
var startTime = Date.now(),
|
||||
prevFileSize = Buffer.byteLength(data, 'utf8');
|
||||
|
||||
@ -401,7 +403,7 @@ function processSVGData(config, info, data, output, input) {
|
||||
try {
|
||||
result = optimize(data, { ...config, ...info });
|
||||
} catch (error) {
|
||||
if (error.name === 'SvgoParserError') {
|
||||
if (error instanceof SvgoParserError) {
|
||||
console.error(colors.red(error.toString()));
|
||||
process.exit(1);
|
||||
} else {
|
||||
@ -441,7 +443,7 @@ function processSVGData(config, info, data, output, input) {
|
||||
* @param {string} input
|
||||
* @param {string} output output file name. '-' for stdout
|
||||
* @param {string} data data to write
|
||||
* @return {Promise}
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
function writeOutput(input, output, data) {
|
||||
if (output == '-') {
|
||||
@ -488,11 +490,11 @@ function printProfitInfo(inBytes, outBytes) {
|
||||
/**
|
||||
* Check for errors, if it's a dir optimize the dir.
|
||||
*
|
||||
* @param {Object} config
|
||||
* @param {any} config
|
||||
* @param {string} input
|
||||
* @param {string} output
|
||||
* @param {Error} error
|
||||
* @return {Promise}
|
||||
* @param {Error & { code: string, path: string }} error
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
function checkOptimizeFileError(config, input, output, error) {
|
||||
if (error.code == 'EISDIR') {
|
||||
@ -511,8 +513,8 @@ function checkOptimizeFileError(config, input, output, error) {
|
||||
* @param {string} input
|
||||
* @param {string} output
|
||||
* @param {string} data
|
||||
* @param {Error} error
|
||||
* @return {Promise}
|
||||
* @param {Error & { code: string }} error
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
function checkWriteFileError(input, output, data, error) {
|
||||
if (error.code == 'EISDIR' && input) {
|
||||
|
@ -1,49 +1,95 @@
|
||||
/**
|
||||
* @typedef {import('../types.js').XastChild} XastChild
|
||||
* @typedef {import('../types.js').XastElement} XastElement
|
||||
* @typedef {import('../types.js').XastNode} XastNode
|
||||
* @typedef {import('../types.js').XastParent} XastParent
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {XastNode} node
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isTag = (node) => {
|
||||
return node.type === 'element';
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {<T>(v: T) => boolean} test
|
||||
* @param {XastNode[]} elems
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const existsOne = (test, elems) => {
|
||||
return elems.some((elem) => {
|
||||
if (isTag(elem)) {
|
||||
return test(elem) || existsOne(test, getChildren(elem));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return isTag(elem) && (test(elem) || existsOne(test, getChildren(elem)));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {XastElement} elem
|
||||
* @param {string} name
|
||||
* @returns {?string}
|
||||
*/
|
||||
const getAttributeValue = (elem, name) => {
|
||||
return elem.attributes[name];
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {XastNode & { children?: XastChild[] }} node
|
||||
* @returns {XastChild[]}
|
||||
*/
|
||||
const getChildren = (node) => {
|
||||
return node.children || [];
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {XastElement} elemAst
|
||||
* @returns {string}
|
||||
*/
|
||||
const getName = (elemAst) => {
|
||||
return elemAst.name;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {XastNode & { parentNode?: XastParent }} node
|
||||
* @returns {?XastParent}
|
||||
*/
|
||||
const getParent = (node) => {
|
||||
return node.parentNode || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {XastElement} elem
|
||||
* @returns {XastChild[]}
|
||||
*/
|
||||
const getSiblings = (elem) => {
|
||||
var parent = getParent(elem);
|
||||
const parent = getParent(elem);
|
||||
return parent ? getChildren(parent) : [];
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {XastParent} node
|
||||
* @returns {string}
|
||||
*/
|
||||
const getText = (node) => {
|
||||
if (node.children[0].type === 'text' && node.children[0].type === 'cdata') {
|
||||
if (node.children[0].type === 'text' || node.children[0].type === 'cdata') {
|
||||
return node.children[0].value;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {XastElement} elem
|
||||
* @param {string} name
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const hasAttrib = (elem, name) => {
|
||||
return elem.attributes[name] !== undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Array<?XastNode>} nodes
|
||||
* @returns {Array<?XastNode>}
|
||||
*/
|
||||
const removeSubsets = (nodes) => {
|
||||
let idx = nodes.length;
|
||||
let node;
|
||||
@ -72,6 +118,11 @@ const removeSubsets = (nodes) => {
|
||||
return nodes;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {<T>(v: T) => boolean} test
|
||||
* @param {XastNode[]} elems
|
||||
* @returns {XastNode[]}
|
||||
*/
|
||||
const findAll = (test, elems) => {
|
||||
const result = [];
|
||||
for (const elem of elems) {
|
||||
@ -85,6 +136,11 @@ const findAll = (test, elems) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {<T>(v: T) => boolean} test
|
||||
* @param {XastNode[]} elems
|
||||
* @returns {?XastNode}
|
||||
*/
|
||||
const findOne = (test, elems) => {
|
||||
for (const elem of elems) {
|
||||
if (isTag(elem)) {
|
||||
@ -100,7 +156,7 @@ const findOne = (test, elems) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const svgoCssSelectAdapter = {
|
||||
export default {
|
||||
isTag,
|
||||
existsOne,
|
||||
getAttributeValue,
|
||||
@ -114,5 +170,3 @@ const svgoCssSelectAdapter = {
|
||||
findAll,
|
||||
findOne,
|
||||
};
|
||||
|
||||
export default svgoCssSelectAdapter;
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { visit } from '../xast.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../svgo').BuiltinPlugin<string, Object>} BuiltinPlugin
|
||||
* @typedef {import('../svgo').BuiltinPluginOrPreset<?, ?>} BuiltinPreset
|
||||
* @typedef {import('../svgo.js').BuiltinPlugin<string, ?>} BuiltinPlugin
|
||||
* @typedef {import('../svgo.js').BuiltinPluginOrPreset<?, ?>} BuiltinPreset
|
||||
* @typedef {import('../types.js').XastNode} XastNode
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -10,10 +11,11 @@ import { visit } from '../xast.js';
|
||||
*
|
||||
* @module plugins
|
||||
*
|
||||
* @param {Object} ast input ast
|
||||
* @param {Object} info extra information
|
||||
* @param {Array} plugins plugins object from config
|
||||
* @return {Object} output ast
|
||||
* @param {XastNode} ast Input AST.
|
||||
* @param {Object} info Extra information.
|
||||
* @param {Array<any>} plugins Plugins property from config.
|
||||
* @param {any} overrides
|
||||
* @param {any} globalOverrides
|
||||
*/
|
||||
export const invokePlugins = (
|
||||
ast,
|
||||
|
@ -116,6 +116,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.3.0",
|
||||
"@jest/globals": "^29.7.0",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
@ -135,7 +136,7 @@
|
||||
"rimraf": "^5.0.7",
|
||||
"rollup": "^4.17.2",
|
||||
"tar-stream": "^3.1.7",
|
||||
"typescript": "^5.4.5"
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"sax@^1.4.1": "patch:sax@npm%3A1.4.1#./.yarn/patches/sax-npm-1.4.1-503b1923cb.patch"
|
||||
|
@ -54,9 +54,7 @@ export const fn = (_root, params) => {
|
||||
if (match) {
|
||||
// round it to the fixed precision
|
||||
let num = Number(Number(match[1]).toFixed(floatPrecision));
|
||||
/**
|
||||
* @type {any}
|
||||
*/
|
||||
/** @type {any} */
|
||||
let matchedUnit = match[3] || '';
|
||||
/**
|
||||
* @type{'' | keyof typeof absoluteLengths}
|
||||
|
@ -60,13 +60,9 @@ export const fn = (_root, params) => {
|
||||
if (match) {
|
||||
// round it to the fixed precision
|
||||
let num = Number(Number(match[1]).toFixed(floatPrecision));
|
||||
/**
|
||||
* @type {any}
|
||||
*/
|
||||
/** @type {any} */
|
||||
let matchedUnit = match[3] || '';
|
||||
/**
|
||||
* @type{'' | keyof typeof absoluteLengths}
|
||||
*/
|
||||
/** @type {'' | keyof typeof absoluteLengths} */
|
||||
let units = matchedUnit;
|
||||
|
||||
// convert absolute values to pixels
|
||||
|
@ -78,7 +78,6 @@ export const fn = (root, params) => {
|
||||
|
||||
const cssText = node.children
|
||||
.filter((child) => child.type === 'text' || child.type === 'cdata')
|
||||
// @ts-expect-error
|
||||
.map((child) => child.value)
|
||||
.join('');
|
||||
|
||||
@ -368,7 +367,6 @@ export const fn = (root, params) => {
|
||||
},
|
||||
});
|
||||
|
||||
// csstree v2 changed this type
|
||||
if (style.cssAst.children.isEmpty) {
|
||||
// remove empty style element
|
||||
detachNodeFromParent(style.node, style.parentNode);
|
||||
|
@ -9,15 +9,5 @@
|
||||
"strict": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["lib/**/*.js", "plugins/**/*", "test/cli/**/*"],
|
||||
"exclude": [
|
||||
"lib/svgo-node.js",
|
||||
"lib/svgo-node.test.js",
|
||||
"lib/svgo.js",
|
||||
"lib/builtin.js",
|
||||
"lib/svgo.test.js",
|
||||
"lib/svgo/**/*.js",
|
||||
"plugins/plugins.js",
|
||||
"plugins/preset-default.js"
|
||||
]
|
||||
"include": ["lib/**/*.js", "plugins/**/*", "test/cli/**/*"]
|
||||
}
|
||||
|
19
yarn.lock
19
yarn.lock
@ -4491,6 +4491,7 @@ __metadata:
|
||||
resolution: "svgo@workspace:."
|
||||
dependencies:
|
||||
"@eslint/js": ^9.3.0
|
||||
"@jest/globals": ^29.7.0
|
||||
"@rollup/plugin-commonjs": ^25.0.7
|
||||
"@rollup/plugin-node-resolve": ^15.2.3
|
||||
"@rollup/plugin-terser": ^0.4.4
|
||||
@ -4517,7 +4518,7 @@ __metadata:
|
||||
rollup: ^4.17.2
|
||||
sax: ^1.4.1
|
||||
tar-stream: ^3.1.7
|
||||
typescript: ^5.4.5
|
||||
typescript: ^5.8.3
|
||||
bin:
|
||||
svgo: ./bin/svgo.js
|
||||
languageName: unknown
|
||||
@ -4626,23 +4627,23 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@npm:^5.4.5":
|
||||
version: 5.4.5
|
||||
resolution: "typescript@npm:5.4.5"
|
||||
"typescript@npm:^5.8.3":
|
||||
version: 5.8.3
|
||||
resolution: "typescript@npm:5.8.3"
|
||||
bin:
|
||||
tsc: bin/tsc
|
||||
tsserver: bin/tsserver
|
||||
checksum: 53c879c6fa1e3bcb194b274d4501ba1985894b2c2692fa079db03c5a5a7140587a1e04e1ba03184605d35f439b40192d9e138eb3279ca8eee313c081c8bcd9b0
|
||||
checksum: cb1d081c889a288b962d3c8ae18d337ad6ee88a8e81ae0103fa1fecbe923737f3ba1dbdb3e6d8b776c72bc73bfa6d8d850c0306eed1a51377d2fccdfd75d92c4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@patch:typescript@^5.4.5#~builtin<compat/typescript>":
|
||||
version: 5.4.5
|
||||
resolution: "typescript@patch:typescript@npm%3A5.4.5#~builtin<compat/typescript>::version=5.4.5&hash=5adc0c"
|
||||
"typescript@patch:typescript@^5.8.3#~builtin<compat/typescript>":
|
||||
version: 5.8.3
|
||||
resolution: "typescript@patch:typescript@npm%3A5.8.3#~builtin<compat/typescript>::version=5.8.3&hash=5786d5"
|
||||
bin:
|
||||
tsc: bin/tsc
|
||||
tsserver: bin/tsserver
|
||||
checksum: d59e26e74f6b444517d0ba16e0ee16e75c652c2b49a59f2ebdbeb16647a855fd50c7fc786a58987e45f03bce0677092e2dd3333649fd53b11d0b0d271455837c
|
||||
checksum: f1743b6850976b3debf7cbd53d2bc0b67e75d47eb6410db564c8bb475e92a8a48f8a3fcd14d89cf93426835281c31a8f8a94dad90be4dc899279a898532ba97f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Reference in New Issue
Block a user