Skip to content

Commit 59796a8

Browse files
authored
feat(removeDeprecatedAttrs): new removeDeprecatedAttrs plugin (#1869)
1 parent 8afb370 commit 59796a8

15 files changed

+416
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
title: Remove Deprecated Attributes
3+
svgo:
4+
pluginId: removeDeprecatedAttrs
5+
defaultPlugin: true
6+
parameters:
7+
removeAny:
8+
description: By default, this plugin only removes safe deprecated attributes that do not change the rendered image. Enabling this will remove all deprecated attributes which may impact rendering.
9+
type: boolean
10+
default: false
11+
---
12+
13+
Removes deprecated attributes from elements in the document.
14+
15+
This plugin does not remove attributes from the deprecated XLink namespace. To remove them, use the [Remove XLink](/docs/plugins/remove-xlink/) plugin.
16+
17+
## Usage
18+
19+
<PluginUsage />
20+
21+
## Demo
22+
23+
<PluginDemo />
24+
25+
## Implementation
26+
27+
- https://github.com/svg/svgo/blob/main/plugins/removeDeprecatedAttrs.js

lib/builtin.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import * as prefixIds from '../plugins/prefixIds.js';
2424
import * as removeAttributesBySelector from '../plugins/removeAttributesBySelector.js';
2525
import * as removeAttrs from '../plugins/removeAttrs.js';
2626
import * as removeComments from '../plugins/removeComments.js';
27+
import * as removeDeprecatedAttrs from '../plugins/removeDeprecatedAttrs.js';
2728
import * as removeDesc from '../plugins/removeDesc.js';
2829
import * as removeDimensions from '../plugins/removeDimensions.js';
2930
import * as removeDoctype from '../plugins/removeDoctype.js';
@@ -79,6 +80,7 @@ export const builtin = [
7980
removeAttributesBySelector,
8081
removeAttrs,
8182
removeComments,
83+
removeDeprecatedAttrs,
8284
removeDesc,
8385
removeDimensions,
8486
removeDoctype,

plugins/_collections.js

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,11 +375,35 @@ export const attrsGroupsDefaults = {
375375
},
376376
};
377377

378+
/**
379+
* @type {Record<string, { safe?: Set<string>, unsafe?: Set<string> }>}
380+
* @see https://www.w3.org/TR/SVG11/intro.html#Definitions
381+
*/
382+
export const attrsGroupsDeprecated = {
383+
animationAttributeTarget: { unsafe: new Set(['attributeType']) },
384+
conditionalProcessing: { unsafe: new Set(['requiredFeatures']) },
385+
core: { unsafe: new Set(['xml:base', 'xml:lang', 'xml:space']) },
386+
presentation: {
387+
unsafe: new Set([
388+
'clip',
389+
'color-profile',
390+
'enable-background',
391+
'glyph-orientation-horizontal',
392+
'glyph-orientation-vertical',
393+
'kerning',
394+
]),
395+
},
396+
};
397+
378398
/**
379399
* @type {Record<string, {
380400
* attrsGroups: Set<string>,
381401
* attrs?: Set<string>,
382402
* defaults?: Record<string, string>,
403+
* deprecated?: {
404+
* safe?: Set<string>,
405+
* unsafe?: Set<string>,
406+
* },
383407
* contentGroups?: Set<string>,
384408
* content?: Set<string>,
385409
* }>}
@@ -574,6 +598,7 @@ export const elems = {
574598
name: 'sRGB',
575599
'rendering-intent': 'auto',
576600
},
601+
deprecated: { unsafe: new Set(['name']) },
577602
contentGroups: new Set(['descriptive']),
578603
},
579604
cursor: {
@@ -958,6 +983,7 @@ export const elems = {
958983
width: '120%',
959984
height: '120%',
960985
},
986+
deprecated: { unsafe: new Set(['filterRes']) },
961987
contentGroups: new Set(['descriptive', 'filterPrimitive']),
962988
content: new Set(['animate', 'set']),
963989
},
@@ -978,6 +1004,15 @@ export const elems = {
9781004
'horiz-origin-x': '0',
9791005
'horiz-origin-y': '0',
9801006
},
1007+
deprecated: {
1008+
unsafe: new Set([
1009+
'horiz-origin-x',
1010+
'horiz-origin-y',
1011+
'vert-adv-y',
1012+
'vert-origin-x',
1013+
'vert-origin-y',
1014+
]),
1015+
},
9811016
contentGroups: new Set(['descriptive']),
9821017
content: new Set(['font-face', 'glyph', 'hkern', 'missing-glyph', 'vkern']),
9831018
},
@@ -1028,6 +1063,31 @@ export const elems = {
10281063
'panose-1': '0 0 0 0 0 0 0 0 0 0',
10291064
slope: '0',
10301065
},
1066+
deprecated: {
1067+
unsafe: new Set([
1068+
'accent-height',
1069+
'alphabetic',
1070+
'ascent',
1071+
'bbox',
1072+
'cap-height',
1073+
'descent',
1074+
'hanging',
1075+
'ideographic',
1076+
'mathematical',
1077+
'panose-1',
1078+
'slope',
1079+
'stemh',
1080+
'stemv',
1081+
'unicode-range',
1082+
'units-per-em',
1083+
'v-alphabetic',
1084+
'v-hanging',
1085+
'v-ideographic',
1086+
'v-mathematical',
1087+
'widths',
1088+
'x-height',
1089+
]),
1090+
},
10311091
contentGroups: new Set(['descriptive']),
10321092
content: new Set([
10331093
// TODO: "at most one 'font-face-src' element"
@@ -1038,10 +1098,12 @@ export const elems = {
10381098
'font-face-format': {
10391099
attrsGroups: new Set(['core']),
10401100
attrs: new Set(['string']),
1101+
deprecated: { unsafe: new Set(['string']) },
10411102
},
10421103
'font-face-name': {
10431104
attrsGroups: new Set(['core']),
10441105
attrs: new Set(['name']),
1106+
deprecated: { unsafe: new Set(['name']) },
10451107
},
10461108
'font-face-src': {
10471109
attrsGroups: new Set(['core']),
@@ -1134,6 +1196,18 @@ export const elems = {
11341196
defaults: {
11351197
'arabic-form': 'initial',
11361198
},
1199+
deprecated: {
1200+
unsafe: new Set([
1201+
'arabic-form',
1202+
'glyph-name',
1203+
'horiz-adv-x',
1204+
'orientation',
1205+
'unicode',
1206+
'vert-adv-y',
1207+
'vert-origin-x',
1208+
'vert-origin-y',
1209+
]),
1210+
},
11371211
contentGroups: new Set([
11381212
'animation',
11391213
'descriptive',
@@ -1173,6 +1247,14 @@ export const elems = {
11731247
'vert-origin-x',
11741248
'vert-origin-y',
11751249
]),
1250+
deprecated: {
1251+
unsafe: new Set([
1252+
'horiz-adv-x',
1253+
'vert-adv-y',
1254+
'vert-origin-x',
1255+
'vert-origin-y',
1256+
]),
1257+
},
11761258
contentGroups: new Set([
11771259
'animation',
11781260
'descriptive',
@@ -1236,6 +1318,7 @@ export const elems = {
12361318
hkern: {
12371319
attrsGroups: new Set(['core']),
12381320
attrs: new Set(['u1', 'g1', 'u2', 'g2', 'k']),
1321+
deprecated: { unsafe: new Set(['g1', 'g2', 'k', 'u1', 'u2']) },
12391322
},
12401323
image: {
12411324
attrsGroups: new Set([
@@ -1430,6 +1513,14 @@ export const elems = {
14301513
'vert-origin-x',
14311514
'vert-origin-y',
14321515
]),
1516+
deprecated: {
1517+
unsafe: new Set([
1518+
'horiz-adv-x',
1519+
'vert-adv-y',
1520+
'vert-origin-x',
1521+
'vert-origin-y',
1522+
]),
1523+
},
14331524
contentGroups: new Set([
14341525
'animation',
14351526
'descriptive',
@@ -1710,6 +1801,15 @@ export const elems = {
17101801
contentScriptType: 'application/ecmascript',
17111802
contentStyleType: 'text/css',
17121803
},
1804+
deprecated: {
1805+
safe: new Set(['version']),
1806+
unsafe: new Set([
1807+
'baseProfile',
1808+
'contentScriptType',
1809+
'contentStyleType',
1810+
'zoomAndPan',
1811+
]),
1812+
},
17131813
contentGroups: new Set([
17141814
'animation',
17151815
'descriptive',
@@ -1956,11 +2056,13 @@ export const elems = {
19562056
'viewTarget',
19572057
'zoomAndPan',
19582058
]),
2059+
deprecated: { unsafe: new Set(['viewTarget', 'zoomAndPan']) },
19592060
contentGroups: new Set(['descriptive']),
19602061
},
19612062
vkern: {
19622063
attrsGroups: new Set(['core']),
19632064
attrs: new Set(['u1', 'g1', 'u2', 'g2', 'k']),
2065+
deprecated: { unsafe: new Set(['g1', 'g2', 'k', 'u1', 'u2']) },
19642066
},
19652067
};
19662068

plugins/plugins-types.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ type DefaultPlugins = {
144144
removeComments: {
145145
preservePatterns: Array<RegExp | string> | false;
146146
};
147+
removeDeprecatedAttrs: {
148+
removeUnsafe?: boolean;
149+
};
147150
removeDesc: {
148151
removeAny?: boolean;
149152
};

plugins/preset-default.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createPreset } from '../lib/svgo/plugins.js';
22
import * as removeDoctype from './removeDoctype.js';
33
import * as removeXMLProcInst from './removeXMLProcInst.js';
44
import * as removeComments from './removeComments.js';
5+
import * as removeDeprecatedAttrs from './removeDeprecatedAttrs.js';
56
import * as removeMetadata from './removeMetadata.js';
67
import * as removeEditorsNSData from './removeEditorsNSData.js';
78
import * as cleanupAttrs from './cleanupAttrs.js';
@@ -41,6 +42,7 @@ const presetDefault = createPreset({
4142
removeDoctype,
4243
removeXMLProcInst,
4344
removeComments,
45+
removeDeprecatedAttrs,
4446
removeMetadata,
4547
removeEditorsNSData,
4648
cleanupAttrs,

plugins/removeDeprecatedAttrs.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import * as csswhat from 'css-what';
2+
import { attrsGroupsDeprecated, elems } from './_collections.js';
3+
import { collectStylesheet } from '../lib/style.js';
4+
5+
export const name = 'removeDeprecatedAttrs';
6+
export const description = 'removes deprecated attributes';
7+
8+
/**
9+
* @typedef {{ safe?: Set<string>; unsafe?: Set<string> }} DeprecatedAttrs
10+
* @typedef {import('../lib/types.js').XastElement} XastElement
11+
*/
12+
13+
/**
14+
* @param {import('../lib/types.js').Stylesheet} stylesheet
15+
* @returns {Set<string>}
16+
*/
17+
function extractAttributesInStylesheet(stylesheet) {
18+
const attributesInStylesheet = new Set();
19+
20+
stylesheet.rules.forEach((rule) => {
21+
const selectors = csswhat.parse(rule.selector);
22+
selectors.forEach((subselector) => {
23+
subselector.forEach((segment) => {
24+
if (segment.type !== 'attribute') {
25+
return;
26+
}
27+
28+
attributesInStylesheet.add(segment.name);
29+
});
30+
});
31+
});
32+
33+
return attributesInStylesheet;
34+
}
35+
36+
/**
37+
* @param {XastElement} node
38+
* @param {DeprecatedAttrs | undefined} deprecatedAttrs
39+
* @param {import('./plugins-types.js').DefaultPlugins['removeDeprecatedAttrs']} params
40+
* @param {Set<string>} attributesInStylesheet
41+
*/
42+
function processAttributes(
43+
node,
44+
deprecatedAttrs,
45+
params,
46+
attributesInStylesheet,
47+
) {
48+
if (!deprecatedAttrs) {
49+
return;
50+
}
51+
52+
if (deprecatedAttrs.safe) {
53+
deprecatedAttrs.safe.forEach((name) => {
54+
if (attributesInStylesheet.has(name)) {
55+
return;
56+
}
57+
delete node.attributes[name];
58+
});
59+
}
60+
61+
if (params.removeUnsafe && deprecatedAttrs.unsafe) {
62+
deprecatedAttrs.unsafe.forEach((name) => {
63+
if (attributesInStylesheet.has(name)) {
64+
return;
65+
}
66+
delete node.attributes[name];
67+
});
68+
}
69+
}
70+
71+
/**
72+
* Remove deprecated attributes.
73+
*
74+
* @type {import('./plugins-types.js').Plugin<'removeDeprecatedAttrs'>}
75+
*/
76+
export function fn(root, params) {
77+
const stylesheet = collectStylesheet(root);
78+
const attributesInStylesheet = extractAttributesInStylesheet(stylesheet);
79+
80+
return {
81+
element: {
82+
enter: (node) => {
83+
const elemConfig = elems[node.name];
84+
if (!elemConfig) {
85+
return;
86+
}
87+
88+
// Special cases
89+
90+
// Removing deprecated xml:lang is safe when the lang attribute exists.
91+
if (
92+
elemConfig.attrsGroups.has('core') &&
93+
node.attributes['xml:lang'] &&
94+
!attributesInStylesheet.has('xml:lang') &&
95+
node.attributes['lang']
96+
) {
97+
delete node.attributes['xml:lang'];
98+
}
99+
100+
// General cases
101+
102+
elemConfig.attrsGroups.forEach((attrsGroup) => {
103+
processAttributes(
104+
node,
105+
attrsGroupsDeprecated[attrsGroup],
106+
params,
107+
attributesInStylesheet,
108+
);
109+
});
110+
111+
processAttributes(
112+
node,
113+
elemConfig.deprecated,
114+
params,
115+
attributesInStylesheet,
116+
);
117+
},
118+
},
119+
};
120+
}

0 commit comments

Comments
 (0)