Skip to content

Commit d5ec128

Browse files
committed
fix: improve jsdoc types and remove excludes
1 parent 10c4287 commit d5ec128

17 files changed

+252
-172
lines changed

lib/parser.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
import SAX from 'sax';
1515
import { textElems } from '../plugins/_collections.js';
1616

17-
class SvgoParserError extends Error {
17+
export class SvgoParserError extends Error {
1818
/**
19-
* @param message {string}
20-
* @param line {number}
21-
* @param column {number}
22-
* @param source {string}
23-
* @param file {void | string}
19+
* @param {string} message
20+
* @param {number} line
21+
* @param {number} column
22+
* @param {string} source
23+
* @param {string|undefined} file
2424
*/
25-
constructor(message, line, column, source, file) {
25+
constructor(message, line, column, source, file = undefined) {
2626
super(message);
2727
this.name = 'SvgoParserError';
2828
this.message = `${file || '<input>'}:${line}:${column}: ${message}`;
@@ -34,6 +34,7 @@ class SvgoParserError extends Error {
3434
Error.captureStackTrace(this, SvgoParserError);
3535
}
3636
}
37+
3738
toString() {
3839
const lines = this.source.split(/\r?\n/);
3940
const startLine = Math.max(this.line - 3, 0);

lib/stringifier.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,10 @@ const entities = {
6767
/**
6868
* convert XAST to SVG string
6969
*
70-
* @type {(data: XastRoot, config: StringifyOptions) => string}
70+
* @type {(data: XastRoot, userOptions?: StringifyOptions) => string}
7171
*/
7272
export const stringifySvg = (data, userOptions = {}) => {
73-
/**
74-
* @type {Options}
75-
*/
73+
/** @type {Options} */
7674
const config = { ...defaults, ...userOptions };
7775
const indent = config.indent;
7876
let newIndent = ' ';
@@ -81,9 +79,7 @@ export const stringifySvg = (data, userOptions = {}) => {
8179
} else if (typeof indent === 'string') {
8280
newIndent = indent;
8381
}
84-
/**
85-
* @type {State}
86-
*/
82+
/** @type {State} */
8783
const state = {
8884
indent: newIndent,
8985
textContext: null,

lib/svgo-node.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Config } from './svgo';
1+
import type { Config } from './svgo.js';
22

3-
export * from './svgo';
3+
export * from './svgo.js';
44

55
/**
66
* If you write a tool on top of svgo you might need a way to load svgo config.

lib/svgo-node.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import os from 'os';
22
import fs from 'fs';
3-
import { pathToFileURL } from 'url';
43
import path from 'path';
54
import {
65
VERSION,
@@ -11,10 +10,17 @@ import {
1110
_collections,
1211
} from './svgo.js';
1312

13+
/**
14+
* @typedef {import('./svgo.js').Config} Config
15+
* @typedef {import('./svgo.js').Output} Output
16+
*/
17+
18+
/**
19+
* @param {string} configFile
20+
* @returns {Promise<Config>}
21+
*/
1422
const importConfig = async (configFile) => {
15-
// dynamic import expects file url instead of path and may fail
16-
// when windows path is provided
17-
const imported = await import(pathToFileURL(configFile));
23+
const imported = await import(path.resolve(configFile));
1824
const config = imported.default;
1925

2026
if (config == null || typeof config !== 'object' || Array.isArray(config)) {
@@ -23,6 +29,10 @@ const importConfig = async (configFile) => {
2329
return config;
2430
};
2531

32+
/**
33+
* @param {string} file
34+
* @returns {Promise<boolean>}
35+
*/
2636
const isFile = async (file) => {
2737
try {
2838
const stats = await fs.promises.stat(file);
@@ -40,6 +50,11 @@ export {
4050
_collections,
4151
};
4252

53+
/**
54+
* @param {string} configFile
55+
* @param {string} cwd
56+
* @returns {Promise<?Config>}
57+
*/
4358
export const loadConfig = async (configFile, cwd = process.cwd()) => {
4459
if (configFile != null) {
4560
if (path.isAbsolute(configFile)) {
@@ -71,6 +86,11 @@ export const loadConfig = async (configFile, cwd = process.cwd()) => {
7186
}
7287
};
7388

89+
/**
90+
* @param {string} input
91+
* @param {Config} config
92+
* @returns {Output}
93+
*/
7494
export const optimize = (input, config) => {
7595
if (config == null) {
7696
config = {};

lib/svgo-node.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import path from 'path';
33
import { optimize, loadConfig } from './svgo-node.js';
44

55
/**
6-
* @typedef {import('../lib/types.js').Plugin} Plugin
6+
* @typedef {import('../lib/types.js').Plugin<?>} Plugin
77
*/
88

99
const describeLF = os.EOL === '\r\n' ? describe.skip : describe;

lib/svgo.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export type PluginConfig =
3333
}[keyof BuiltinsWithRequiredParams]
3434
| CustomPlugin;
3535

36-
type BuiltinPlugin<Name, Params> = {
36+
export type BuiltinPlugin<Name, Params> = {
3737
/** Name of the plugin, also known as the plugin ID. */
3838
name: Name;
3939
description?: string;

lib/svgo.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import _collections from '../plugins/_collections.js';
99

1010
/**
1111
* @typedef {import('./svgo.js').BuiltinPluginOrPreset<?, ?>} BuiltinPluginOrPreset
12+
* @typedef {import('./svgo.js').Config} Config
13+
* @typedef {import('./svgo.js').Output} Output
14+
* @typedef {import('./svgo.js').PluginConfig} PluginConfig
1215
*/
1316

1417
const pluginsMap = new Map();
@@ -31,6 +34,10 @@ function getPlugin(name) {
3134
return pluginsMap.get(name);
3235
}
3336

37+
/**
38+
* @param {string|PluginConfig} plugin
39+
* @returns {?PluginConfig}
40+
*/
3441
const resolvePluginConfig = (plugin) => {
3542
if (typeof plugin === 'string') {
3643
// resolve builtin plugin specified as string
@@ -49,6 +56,7 @@ const resolvePluginConfig = (plugin) => {
4956
throw Error(`Plugin name must be specified`);
5057
}
5158
// use custom plugin implementation
59+
// @ts-expect-error Checking for CustomPlugin with the presence of fn
5260
let fn = plugin.fn;
5361
if (fn == null) {
5462
// resolve builtin plugin implementation
@@ -75,6 +83,11 @@ export {
7583
_collections,
7684
};
7785

86+
/**
87+
* @param {string} input
88+
* @param {Config} config
89+
* @returns {Output}
90+
*/
7891
export const optimize = (input, config) => {
7992
if (config == null) {
8093
config = {};
@@ -107,6 +120,8 @@ export const optimize = (input, config) => {
107120
'Warning: plugins list includes null or undefined elements, these will be ignored.',
108121
);
109122
}
123+
124+
/** @type {Config} */
110125
const globalOverrides = {};
111126
if (config.floatPrecision != null) {
112127
globalOverrides.floatPrecision = config.floatPrecision;

lib/svgo.test.js

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { jest } from '@jest/globals';
22
import { optimize } from './svgo.js';
3+
import { SvgoParserError } from './parser.js';
4+
5+
/**
6+
* @typedef {import('./svgo.js').CustomPlugin} CustomPlugin
7+
*/
38

49
test('allow to setup default preset', () => {
510
const svg = `
@@ -65,6 +70,7 @@ test('warn when user tries enable plugins in preset', () => {
6570
const warn = jest.spyOn(console, 'warn');
6671
optimize(svg, {
6772
plugins: [
73+
// @ts-expect-error Testing if we receive config that diverges from type definitions.
6874
{
6975
name: 'preset-default',
7076
params: {
@@ -138,6 +144,7 @@ describe('allow to configure EOL', () => {
138144
</svg>
139145
`;
140146
const { data } = optimize(svg, {
147+
// @ts-expect-error Testing if we receive config that diverges from type definitions.
141148
js2svg: { eol: 'invalid', pretty: true, indent: 2 },
142149
});
143150
expect(data).toBe(
@@ -256,46 +263,47 @@ test('plugin precision should override preset precision', () => {
256263
});
257264

258265
test('provides informative error in result', () => {
259-
expect.assertions(6);
260266
const svg = `<svg viewBox="0 0 120 120">
261267
<circle fill="#ff0000" cx=60.444444" cy="60" r="50"/>
262268
</svg>
263269
`;
264-
try {
265-
optimize(svg, { path: 'test.svg' });
266-
} catch (error) {
267-
expect(error.name).toBe('SvgoParserError');
268-
expect(error.message).toBe('test.svg:2:33: Unquoted attribute value');
269-
expect(error.reason).toBe('Unquoted attribute value');
270-
expect(error.line).toBe(2);
271-
expect(error.column).toBe(33);
272-
expect(error.source).toBe(svg);
273-
}
270+
const error = new SvgoParserError(
271+
'Unquoted attribute value',
272+
2,
273+
33,
274+
svg,
275+
'test.svg',
276+
);
277+
expect(() => optimize(svg, { path: 'test.svg' })).toThrow(error);
278+
expect(error.name).toBe('SvgoParserError');
279+
expect(error.message).toBe('test.svg:2:33: Unquoted attribute value');
274280
});
275281

276282
test('provides code snippet in rendered error', () => {
277-
expect.assertions(1);
278283
const svg = `<svg viewBox="0 0 120 120">
279284
<circle fill="#ff0000" cx=60.444444" cy="60" r="50"/>
280285
</svg>
281286
`;
282-
try {
283-
optimize(svg, { path: 'test.svg' });
284-
} catch (error) {
285-
expect(error.toString())
286-
.toBe(`SvgoParserError: test.svg:2:29: Unquoted attribute value
287+
const error = new SvgoParserError(
288+
'Unquoted attribute value',
289+
2,
290+
29,
291+
svg,
292+
'test.svg',
293+
);
294+
expect(() => optimize(svg, { path: 'test.svg' })).toThrow(error);
295+
expect(error.toString())
296+
.toBe(`SvgoParserError: test.svg:2:29: Unquoted attribute value
287297
288298
1 | <svg viewBox="0 0 120 120">
289299
> 2 | <circle fill="#ff0000" cx=60.444444" cy="60" r="50"/>
290300
| ^
291301
3 | </svg>
292302
4 |
293303
`);
294-
}
295304
});
296305

297306
test('supports errors without path', () => {
298-
expect.assertions(1);
299307
const svg = `<svg viewBox="0 0 120 120">
300308
<circle/>
301309
<circle/>
@@ -309,11 +317,10 @@ test('supports errors without path', () => {
309317
<circle fill="#ff0000" cx=60.444444" cy="60" r="50"/>
310318
</svg>
311319
`;
312-
try {
313-
optimize(svg);
314-
} catch (error) {
315-
expect(error.toString())
316-
.toBe(`SvgoParserError: <input>:11:29: Unquoted attribute value
320+
const error = new SvgoParserError('Unquoted attribute value', 11, 29, svg);
321+
expect(() => optimize(svg)).toThrow(error);
322+
expect(error.toString())
323+
.toBe(`SvgoParserError: <input>:11:29: Unquoted attribute value
317324
318325
9 | <circle/>
319326
10 | <circle/>
@@ -322,33 +329,32 @@ test('supports errors without path', () => {
322329
12 | </svg>
323330
13 |
324331
`);
325-
}
326332
});
327333

328334
test('slices long line in error code snippet', () => {
329-
expect.assertions(1);
330335
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">
331336
<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" />
332337
</svg>
333338
`;
334-
try {
335-
optimize(svg);
336-
} catch (error) {
337-
expect(error.toString())
338-
.toBe(`SvgoParserError: <input>:2:211: Invalid attribute name
339+
const error = new SvgoParserError('Invalid attribute name', 2, 211, svg);
340+
341+
expect(() => optimize(svg)).toThrow(error);
342+
expect(error.toString())
343+
.toBe(`SvgoParserError: <input>:2:211: Invalid attribute name
339344
340345
1 | …-0.dtd" viewBox="0 0 230 120">
341346
> 2 | …7.334 250.955 222.534t579.555 155.292z stroke-width="1.5" fill="red" strok…
342347
| ^
343348
3 |
344349
4 |
345350
`);
346-
}
347351
});
348352

349353
test('multipass option should trigger plugins multiple times', () => {
350354
const svg = `<svg id="abcdefghijklmnopqrstuvwxyz"></svg>`;
355+
/** @type {number[]} */
351356
const list = [];
357+
/** @type {CustomPlugin} */
352358
const testPlugin = {
353359
name: 'testPlugin',
354360
fn: (_root, _params, info) => {

0 commit comments

Comments
 (0)