mirror of
https://github.com/svg/svgo.git
synced 2025-07-29 20:21:14 +03:00
Add better parser errors (#1553)
Old SVGO errors were not very helpful. Packages like cssnano (postcss-svgo) had to deal with a lot of issues which are hard to debug with old errors. ``` Error: Error in parsing SVG: Unquoted attribute value Line: 1 Column: 29 Char: 6 File: input.svg ``` New errors are more informative and may solve many struggles ``` Error: SvgoParserError: input.svg:2:29: Unquoted attribute value 1 | <svg viewBox="0 0 120 120"> > 2 | <circle fill="#ff0000" cx=60.444444" cy="60" r="50"/> | ^ 3 | </svg> 4 | ```
This commit is contained in:
@ -29,7 +29,12 @@ const optimize = (input, config) => {
|
|||||||
}
|
}
|
||||||
for (let i = 0; i < maxPassCount; i += 1) {
|
for (let i = 0; i < maxPassCount; i += 1) {
|
||||||
info.multipassCount = i;
|
info.multipassCount = i;
|
||||||
svgjs = svg2js(input);
|
// TODO throw this error in v3
|
||||||
|
try {
|
||||||
|
svgjs = svg2js(input, config.path);
|
||||||
|
} catch (error) {
|
||||||
|
return { error: error.toString(), modernError: error };
|
||||||
|
}
|
||||||
if (svgjs.error != null) {
|
if (svgjs.error != null) {
|
||||||
if (config.path != null) {
|
if (config.path != null) {
|
||||||
svgjs.path = config.path;
|
svgjs.path = config.path;
|
||||||
|
184
lib/svgo.test.js
184
lib/svgo.test.js
@ -12,12 +12,11 @@ test('allow to setup default preset', () => {
|
|||||||
<circle fill="#ff0000" cx="60" cy="60" r="50"/>
|
<circle fill="#ff0000" cx="60" cy="60" r="50"/>
|
||||||
</svg>
|
</svg>
|
||||||
`;
|
`;
|
||||||
expect(
|
const { data } = optimize(svg, {
|
||||||
optimize(svg, {
|
plugins: ['preset-default'],
|
||||||
plugins: ['preset-default'],
|
js2svg: { pretty: true, indent: 2 },
|
||||||
js2svg: { pretty: true, indent: 2 },
|
});
|
||||||
}).data
|
expect(data).toMatchInlineSnapshot(`
|
||||||
).toMatchInlineSnapshot(`
|
|
||||||
"<svg viewBox=\\"0 0 120 120\\">
|
"<svg viewBox=\\"0 0 120 120\\">
|
||||||
<circle fill=\\"red\\" cx=\\"60\\" cy=\\"60\\" r=\\"50\\"/>
|
<circle fill=\\"red\\" cx=\\"60\\" cy=\\"60\\" r=\\"50\\"/>
|
||||||
</svg>
|
</svg>
|
||||||
@ -35,24 +34,23 @@ test('allow to disable and customize plugins in preset', () => {
|
|||||||
<circle fill="#ff0000" cx="60" cy="60" r="50"/>
|
<circle fill="#ff0000" cx="60" cy="60" r="50"/>
|
||||||
</svg>
|
</svg>
|
||||||
`;
|
`;
|
||||||
expect(
|
const { data } = optimize(svg, {
|
||||||
optimize(svg, {
|
plugins: [
|
||||||
plugins: [
|
{
|
||||||
{
|
name: 'preset-default',
|
||||||
name: 'preset-default',
|
params: {
|
||||||
params: {
|
overrides: {
|
||||||
overrides: {
|
removeXMLProcInst: false,
|
||||||
removeXMLProcInst: false,
|
removeDesc: {
|
||||||
removeDesc: {
|
removeAny: false,
|
||||||
removeAny: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
js2svg: { pretty: true, indent: 2 },
|
],
|
||||||
}).data
|
js2svg: { pretty: true, indent: 2 },
|
||||||
).toMatchInlineSnapshot(`
|
});
|
||||||
|
expect(data).toMatchInlineSnapshot(`
|
||||||
"<?xml version=\\"1.0\\" encoding=\\"utf-8\\"?>
|
"<?xml version=\\"1.0\\" encoding=\\"utf-8\\"?>
|
||||||
<svg viewBox=\\"0 0 120 120\\">
|
<svg viewBox=\\"0 0 120 120\\">
|
||||||
<desc>
|
<desc>
|
||||||
@ -186,19 +184,18 @@ test('allow to customize precision for preset', () => {
|
|||||||
<circle fill="#ff0000" cx="60.444444" cy="60" r="50"/>
|
<circle fill="#ff0000" cx="60.444444" cy="60" r="50"/>
|
||||||
</svg>
|
</svg>
|
||||||
`;
|
`;
|
||||||
expect(
|
const { data } = optimize(svg, {
|
||||||
optimize(svg, {
|
plugins: [
|
||||||
plugins: [
|
{
|
||||||
{
|
name: 'preset-default',
|
||||||
name: 'preset-default',
|
params: {
|
||||||
params: {
|
floatPrecision: 4,
|
||||||
floatPrecision: 4,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
js2svg: { pretty: true, indent: 2 },
|
],
|
||||||
}).data
|
js2svg: { pretty: true, indent: 2 },
|
||||||
).toMatchInlineSnapshot(`
|
});
|
||||||
|
expect(data).toMatchInlineSnapshot(`
|
||||||
"<svg viewBox=\\"0 0 120 120\\">
|
"<svg viewBox=\\"0 0 120 120\\">
|
||||||
<circle fill=\\"red\\" cx=\\"60.4444\\" cy=\\"60\\" r=\\"50\\"/>
|
<circle fill=\\"red\\" cx=\\"60.4444\\" cy=\\"60\\" r=\\"50\\"/>
|
||||||
</svg>
|
</svg>
|
||||||
@ -212,27 +209,118 @@ test('plugin precision should override preset precision', () => {
|
|||||||
<circle fill="#ff0000" cx="60.444444" cy="60" r="50"/>
|
<circle fill="#ff0000" cx="60.444444" cy="60" r="50"/>
|
||||||
</svg>
|
</svg>
|
||||||
`;
|
`;
|
||||||
expect(
|
const { data } = optimize(svg, {
|
||||||
optimize(svg, {
|
plugins: [
|
||||||
plugins: [
|
{
|
||||||
{
|
name: 'preset-default',
|
||||||
name: 'preset-default',
|
params: {
|
||||||
params: {
|
floatPrecision: 4,
|
||||||
floatPrecision: 4,
|
overrides: {
|
||||||
overrides: {
|
cleanupNumericValues: {
|
||||||
cleanupNumericValues: {
|
floatPrecision: 5,
|
||||||
floatPrecision: 5,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
js2svg: { pretty: true, indent: 2 },
|
],
|
||||||
}).data
|
js2svg: { pretty: true, indent: 2 },
|
||||||
).toMatchInlineSnapshot(`
|
});
|
||||||
|
expect(data).toMatchInlineSnapshot(`
|
||||||
"<svg viewBox=\\"0 0 120 120\\">
|
"<svg viewBox=\\"0 0 120 120\\">
|
||||||
<circle fill=\\"red\\" cx=\\"60.44444\\" cy=\\"60\\" r=\\"50\\"/>
|
<circle fill=\\"red\\" cx=\\"60.44444\\" cy=\\"60\\" r=\\"50\\"/>
|
||||||
</svg>
|
</svg>
|
||||||
"
|
"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('provides informative error in result', () => {
|
||||||
|
const svg = `<svg viewBox="0 0 120 120">
|
||||||
|
<circle fill="#ff0000" cx=60.444444" cy="60" r="50"/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
const { modernError: error } = optimize(svg, { path: 'test.svg' });
|
||||||
|
expect(error.name).toEqual('SvgoParserError');
|
||||||
|
expect(error.message).toEqual('test.svg:2:33: Unquoted attribute value');
|
||||||
|
expect(error.reason).toEqual('Unquoted attribute value');
|
||||||
|
expect(error.line).toEqual(2);
|
||||||
|
expect(error.column).toEqual(33);
|
||||||
|
expect(error.source).toEqual(svg);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('provides code snippet in rendered error', () => {
|
||||||
|
const svg = `<svg viewBox="0 0 120 120">
|
||||||
|
<circle fill="#ff0000" cx=60.444444" cy="60" r="50"/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
const { modernError: error } = optimize(svg, { path: 'test.svg' });
|
||||||
|
expect(error.toString())
|
||||||
|
.toEqual(`SvgoParserError: test.svg:2:29: Unquoted attribute value
|
||||||
|
|
||||||
|
1 | <svg viewBox="0 0 120 120">
|
||||||
|
> 2 | <circle fill="#ff0000" cx=60.444444" cy="60" r="50"/>
|
||||||
|
| ^
|
||||||
|
3 | </svg>
|
||||||
|
4 |
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports errors without path', () => {
|
||||||
|
const svg = `<svg viewBox="0 0 120 120">
|
||||||
|
<circle/>
|
||||||
|
<circle/>
|
||||||
|
<circle/>
|
||||||
|
<circle/>
|
||||||
|
<circle/>
|
||||||
|
<circle/>
|
||||||
|
<circle/>
|
||||||
|
<circle/>
|
||||||
|
<circle/>
|
||||||
|
<circle fill="#ff0000" cx=60.444444" cy="60" r="50"/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
const { modernError: error } = optimize(svg);
|
||||||
|
expect(error.toString())
|
||||||
|
.toEqual(`SvgoParserError: <input>:11:29: Unquoted attribute value
|
||||||
|
|
||||||
|
9 | <circle/>
|
||||||
|
10 | <circle/>
|
||||||
|
> 11 | <circle fill="#ff0000" cx=60.444444" cy="60" r="50"/>
|
||||||
|
| ^
|
||||||
|
12 | </svg>
|
||||||
|
13 |
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('slices long line in error code snippet', () => {
|
||||||
|
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>
|
||||||
|
`;
|
||||||
|
const { modernError: error } = optimize(svg);
|
||||||
|
expect(error.toString())
|
||||||
|
.toEqual(`SvgoParserError: <input>:2:211: Invalid attribute name
|
||||||
|
|
||||||
|
1 | …-0.dtd" viewBox="0 0 230 120">
|
||||||
|
> 2 | …7.334 250.955 222.534t579.555 155.292z stroke-width="1.5" fill="red" strok…
|
||||||
|
| ^
|
||||||
|
3 |
|
||||||
|
4 |
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('provides legacy error message', () => {
|
||||||
|
const svg = `<svg viewBox="0 0 120 120">
|
||||||
|
<circle fill="#ff0000" cx=60.444444" cy="60" r="50"/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
const { error } = optimize(svg, { path: 'test.svg' });
|
||||||
|
expect(error)
|
||||||
|
.toEqual(`SvgoParserError: test.svg:2:29: Unquoted attribute value
|
||||||
|
|
||||||
|
1 | <svg viewBox="0 0 120 120">
|
||||||
|
> 2 | <circle fill="#ff0000" cx=60.444444" cy="60" r="50"/>
|
||||||
|
| ^
|
||||||
|
3 | </svg>
|
||||||
|
4 |
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
const FS = require('fs');
|
const FS = require('fs');
|
||||||
const PATH = require('path');
|
const PATH = require('path');
|
||||||
const { green } = require('colorette');
|
const { green, red } = require('colorette');
|
||||||
const { loadConfig, optimize } = require('../svgo-node.js');
|
const { loadConfig, optimize } = require('../svgo-node.js');
|
||||||
const pluginsMap = require('../../plugins/plugins.js');
|
const pluginsMap = require('../../plugins/plugins.js');
|
||||||
const PKG = require('../../package.json');
|
const PKG = require('../../package.json');
|
||||||
@ -383,12 +383,9 @@ function processSVGData(config, info, data, output, input) {
|
|||||||
prevFileSize = Buffer.byteLength(data, 'utf8');
|
prevFileSize = Buffer.byteLength(data, 'utf8');
|
||||||
|
|
||||||
const result = optimize(data, { ...config, ...info });
|
const result = optimize(data, { ...config, ...info });
|
||||||
if (result.error) {
|
if (result.modernError) {
|
||||||
let message = result.error;
|
console.error(red(result.modernError.toString()));
|
||||||
if (result.path != null) {
|
process.exit(1);
|
||||||
message += `\nFile: ${result.path}`;
|
|
||||||
}
|
|
||||||
throw Error(message);
|
|
||||||
}
|
}
|
||||||
if (config.datauri) {
|
if (config.datauri) {
|
||||||
result.data = encodeSVGDatauri(result.data, config.datauri);
|
result.data = encodeSVGDatauri(result.data, config.datauri);
|
||||||
|
@ -4,6 +4,55 @@ const SAX = require('@trysound/sax');
|
|||||||
const JSAPI = require('./jsAPI.js');
|
const JSAPI = require('./jsAPI.js');
|
||||||
const { textElems } = require('../../plugins/_collections.js');
|
const { textElems } = require('../../plugins/_collections.js');
|
||||||
|
|
||||||
|
class SvgoParserError extends Error {
|
||||||
|
constructor(message, line, column, source, file) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'SvgoParserError';
|
||||||
|
this.message = `${file || '<input>'}:${line}:${column}: ${message}`;
|
||||||
|
this.reason = message;
|
||||||
|
this.line = line;
|
||||||
|
this.column = column;
|
||||||
|
this.source = source;
|
||||||
|
if (Error.captureStackTrace) {
|
||||||
|
Error.captureStackTrace(this, SvgoParserError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toString() {
|
||||||
|
const lines = this.source.split(/\r?\n/);
|
||||||
|
const startLine = Math.max(this.line - 3, 0);
|
||||||
|
const endLine = Math.min(this.line + 2, lines.length);
|
||||||
|
const lineNumberWidth = String(endLine).length;
|
||||||
|
const startColumn = Math.max(this.column - 54, 0);
|
||||||
|
const endColumn = Math.max(this.column + 20, 80);
|
||||||
|
const code = lines
|
||||||
|
.slice(startLine, endLine)
|
||||||
|
.map((line, index) => {
|
||||||
|
const lineSlice = line.slice(startColumn, endColumn);
|
||||||
|
let ellipsisPrefix = '';
|
||||||
|
let ellipsisSuffix = '';
|
||||||
|
if (startColumn !== 0) {
|
||||||
|
ellipsisPrefix = startColumn > line.length - 1 ? ' ' : '…';
|
||||||
|
}
|
||||||
|
if (endColumn < line.length - 1) {
|
||||||
|
ellipsisSuffix = '…';
|
||||||
|
}
|
||||||
|
const number = startLine + 1 + index;
|
||||||
|
const gutter = ` ${number.toString().padStart(lineNumberWidth)} | `;
|
||||||
|
if (number === this.line) {
|
||||||
|
const gutterSpacing = gutter.replace(/[^|]/g, ' ');
|
||||||
|
const lineSpacing = (
|
||||||
|
ellipsisPrefix + line.slice(startColumn, this.column - 1)
|
||||||
|
).replace(/[^\t]/g, ' ');
|
||||||
|
const spacing = gutterSpacing + lineSpacing;
|
||||||
|
return `>${gutter}${ellipsisPrefix}${lineSlice}${ellipsisSuffix}\n ${spacing}^`;
|
||||||
|
}
|
||||||
|
return ` ${gutter}${ellipsisPrefix}${lineSlice}${ellipsisSuffix}`;
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
return `${this.name}: ${this.message}\n\n${code}\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const entityDeclaration = /<!ENTITY\s+(\S+)\s+(?:'([^']+)'|"([^"]+)")\s*>/g;
|
const entityDeclaration = /<!ENTITY\s+(\S+)\s+(?:'([^']+)'|"([^"]+)")\s*>/g;
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
@ -20,7 +69,7 @@ const config = {
|
|||||||
*
|
*
|
||||||
* @param {String} data input data
|
* @param {String} data input data
|
||||||
*/
|
*/
|
||||||
module.exports = function (data) {
|
module.exports = function (data, from) {
|
||||||
const sax = SAX.parser(config.strict, config);
|
const sax = SAX.parser(config.strict, config);
|
||||||
const root = new JSAPI({ type: 'root', children: [] });
|
const root = new JSAPI({ type: 'root', children: [] });
|
||||||
let current = root;
|
let current = root;
|
||||||
@ -115,16 +164,18 @@ module.exports = function (data) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
sax.onerror = function (e) {
|
sax.onerror = function (e) {
|
||||||
e.message = 'Error in parsing SVG: ' + e.message;
|
const error = new SvgoParserError(
|
||||||
if (e.message.indexOf('Unexpected end') < 0) {
|
e.reason,
|
||||||
throw e;
|
e.line + 1,
|
||||||
|
e.column,
|
||||||
|
data,
|
||||||
|
from
|
||||||
|
);
|
||||||
|
if (e.message.indexOf('Unexpected end') === -1) {
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
sax.write(data).close();
|
||||||
sax.write(data).close();
|
return root;
|
||||||
return root;
|
|
||||||
} catch (e) {
|
|
||||||
return { error: e.message };
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
79
package-lock.json
generated
79
package-lock.json
generated
@ -617,6 +617,17 @@
|
|||||||
"rimraf": "^3.0.0",
|
"rimraf": "^3.0.0",
|
||||||
"slash": "^3.0.0",
|
"slash": "^3.0.0",
|
||||||
"strip-ansi": "^6.0.0"
|
"strip-ansi": "^6.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"strip-ansi": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ansi-regex": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@jest/environment": {
|
"@jest/environment": {
|
||||||
@ -867,9 +878,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@trysound/sax": {
|
"@trysound/sax": {
|
||||||
"version": "0.1.1",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
|
||||||
"integrity": "sha512-Z6DoceYb/1xSg5+e+ZlPZ9v0N16ZvZ+wYMraFue4HYrE4ttONKtsvruIRf6t9TBR0YvSOfi1hUU0fJfBLCDYow=="
|
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA=="
|
||||||
},
|
},
|
||||||
"@types/babel__core": {
|
"@types/babel__core": {
|
||||||
"version": "7.1.15",
|
"version": "7.1.15",
|
||||||
@ -1403,6 +1414,17 @@
|
|||||||
"string-width": "^4.2.0",
|
"string-width": "^4.2.0",
|
||||||
"strip-ansi": "^6.0.0",
|
"strip-ansi": "^6.0.0",
|
||||||
"wrap-ansi": "^7.0.0"
|
"wrap-ansi": "^7.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"strip-ansi": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ansi-regex": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"co": {
|
"co": {
|
||||||
@ -1852,6 +1874,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
|
||||||
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
|
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
|
},
|
||||||
|
"strip-ansi": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ansi-regex": "^5.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -3946,6 +3977,17 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"char-regex": "^1.0.2",
|
"char-regex": "^1.0.2",
|
||||||
"strip-ansi": "^6.0.0"
|
"strip-ansi": "^6.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"strip-ansi": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ansi-regex": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"string-width": {
|
"string-width": {
|
||||||
@ -3957,6 +3999,17 @@
|
|||||||
"emoji-regex": "^8.0.0",
|
"emoji-regex": "^8.0.0",
|
||||||
"is-fullwidth-code-point": "^3.0.0",
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
"strip-ansi": "^6.0.0"
|
"strip-ansi": "^6.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"strip-ansi": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ansi-regex": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"string_decoder": {
|
"string_decoder": {
|
||||||
@ -4059,6 +4112,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||||
"dev": true
|
"dev": true
|
||||||
|
},
|
||||||
|
"strip-ansi": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ansi-regex": "^5.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -4339,6 +4401,17 @@
|
|||||||
"ansi-styles": "^4.0.0",
|
"ansi-styles": "^4.0.0",
|
||||||
"string-width": "^4.1.0",
|
"string-width": "^4.1.0",
|
||||||
"strip-ansi": "^6.0.0"
|
"strip-ansi": "^6.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"strip-ansi": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ansi-regex": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"wrappy": {
|
"wrappy": {
|
||||||
|
@ -92,7 +92,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@trysound/sax": "0.1.1",
|
"@trysound/sax": "0.2.0",
|
||||||
"colorette": "^1.3.0",
|
"colorette": "^1.3.0",
|
||||||
"commander": "^7.2.0",
|
"commander": "^7.2.0",
|
||||||
"css-select": "^4.1.3",
|
"css-select": "^4.1.3",
|
||||||
@ -118,6 +118,7 @@
|
|||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
"rollup": "^2.56.3",
|
"rollup": "^2.56.3",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
"tar-stream": "^2.2.0",
|
"tar-stream": "^2.2.0",
|
||||||
"typescript": "^4.4.2"
|
"typescript": "^4.4.2"
|
||||||
},
|
},
|
||||||
|
33
test/cli/cli.test.js
Normal file
33
test/cli/cli.test.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
const stripAnsi = require('strip-ansi');
|
||||||
|
|
||||||
|
test('should exit with 1 code on syntax error', async () => {
|
||||||
|
const proc = spawn('node', ['../../bin/svgo', 'invalid.svg'], {
|
||||||
|
cwd: __dirname,
|
||||||
|
});
|
||||||
|
const [code, stderr] = await Promise.all([
|
||||||
|
new Promise((resolve) => {
|
||||||
|
proc.on('close', (code) => {
|
||||||
|
resolve(code);
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
new Promise((resolve) => {
|
||||||
|
proc.stderr.on('data', (error) => {
|
||||||
|
resolve(error.toString());
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
expect(code).toEqual(1);
|
||||||
|
expect(stripAnsi(stderr))
|
||||||
|
.toEqual(`SvgoParserError: invalid.svg:2:27: Unquoted attribute value
|
||||||
|
|
||||||
|
1 | <svg>
|
||||||
|
> 2 | <rect x="0" y="0" width=10" height="20" />
|
||||||
|
| ^
|
||||||
|
3 | </svg>
|
||||||
|
4 |
|
||||||
|
|
||||||
|
`);
|
||||||
|
});
|
3
test/cli/invalid.svg
Normal file
3
test/cli/invalid.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg>
|
||||||
|
<rect x="0" y="0" width=10" height="20" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 58 B |
@ -341,46 +341,4 @@ describe('svg2js', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('malformed svg', function () {
|
|
||||||
var filepath = PATH.resolve(__dirname, './test.bad.svg'),
|
|
||||||
root,
|
|
||||||
error;
|
|
||||||
|
|
||||||
beforeAll(function (done) {
|
|
||||||
FS.readFile(filepath, 'utf8', function (err, data) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
root = SVG2JS(data);
|
|
||||||
} catch (e) {
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('root', function () {
|
|
||||||
it('should have property "error"', function () {
|
|
||||||
expect(root).toHaveProperty('error');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('root.error', function () {
|
|
||||||
it('should be "Error in parsing SVG: Unexpected close tag"', function () {
|
|
||||||
expect(root.error).toEqual(
|
|
||||||
'Error in parsing SVG: Unexpected close tag\nLine: 10\nColumn: 15\nChar: >'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('error', function () {
|
|
||||||
it('should not be thrown', function () {
|
|
||||||
expect(error).not.toEqual(expect.anything());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
width="120px" height="120px" viewBox="0 0 120 120"
|
|
||||||
enable-background="new 0 0 120 120" xml:space="preserve"
|
|
||||||
>
|
|
||||||
style type="text/css"><![CDATA[
|
|
||||||
svg { fill: red; }
|
|
||||||
]]></style>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<circle fill="#ff0000" cx="60px" cy="60px" r="50px"/>
|
|
||||||
<text>test</text>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 692 B |
@ -46,14 +46,6 @@ describe('svgo', () => {
|
|||||||
const result = optimize(original, { input: 'file', path: 'input.svg' });
|
const result = optimize(original, { input: 'file', path: 'input.svg' });
|
||||||
expect(normalize(result.data)).toEqual(expected);
|
expect(normalize(result.data)).toEqual(expected);
|
||||||
});
|
});
|
||||||
it('should handle parse error', async () => {
|
|
||||||
const fixture = await fs.promises.readFile(
|
|
||||||
path.resolve(__dirname, 'invalid.svg')
|
|
||||||
);
|
|
||||||
const result = optimize(fixture, { input: 'file', path: 'input.svg' });
|
|
||||||
expect(result.error).toMatch(/Error in parsing SVG/);
|
|
||||||
expect(result.path).toEqual('input.svg');
|
|
||||||
});
|
|
||||||
it('should handle empty svg tag', async () => {
|
it('should handle empty svg tag', async () => {
|
||||||
const result = optimize('<svg />', { input: 'file', path: 'input.svg' });
|
const result = optimize('<svg />', { input: 'file', path: 'input.svg' });
|
||||||
expect(result.data).toEqual('<svg/>');
|
expect(result.data).toEqual('<svg/>');
|
||||||
|
@ -10,7 +10,12 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"noImplicitAny": true
|
"noImplicitAny": true
|
||||||
},
|
},
|
||||||
"include": ["plugins/**/*", "lib/xast.test.js", "lib/path.test.js"],
|
"include": [
|
||||||
|
"plugins/**/*",
|
||||||
|
"lib/xast.test.js",
|
||||||
|
"lib/path.test.js",
|
||||||
|
"test/cli/**/*"
|
||||||
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"plugins/_applyTransforms.js",
|
"plugins/_applyTransforms.js",
|
||||||
"plugins/collapseGroups.js",
|
"plugins/collapseGroups.js",
|
||||||
|
Reference in New Issue
Block a user