Skip to content

Commit a819211

Browse files
committed
Allow multiple attributes for formComponents and linkComponents settings
1 parent 2c9c85a commit a819211

File tree

5 files changed

+65
-25
lines changed

5 files changed

+65
-25
lines changed

README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,14 @@ You should also specify settings that will be shared across all the plugin rules
6363
"formComponents": [
6464
// Components used as alternatives to <form> for forms, eg. <Form endpoint={ url } />
6565
"CustomForm",
66-
{"name": "Form", "formAttribute": "endpoint"}
66+
{"name": "Form", "formAttributes": ["registerEnpoint", "loginEnpoint"]}, // allows specifying multiple properties if necessary
67+
{"name": "SimpleForm", "formAttribute": "endpoint"}, // deprecated backwards-compatible version
6768
],
6869
"linkComponents": [
6970
// Components used as alternatives to <a> for linking, eg. <Link to={ url } />
7071
"Hyperlink",
71-
{"name": "Link", "linkAttribute": "to"}
72+
{"name": "Link", "linkAttributes": ["to", "href"]} // allows specifying multiple properties if necessary
73+
{"name": "MyLink", "linkAttribute": "to"} // deprecated backwards-compatible version
7274
]
7375
}
7476
}

lib/rules/jsx-no-script-url.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,13 @@ function shouldVerifyProp(node, config) {
2828

2929
if (!name || !parentName || !config.has(parentName)) return false;
3030

31-
return name === config.get(parentName);
31+
const attributes = config.get(parentName)
32+
return attributes.includes(name);
3233
}
3334

3435
function parseLegacyOption(config, option) {
3536
option.forEach((opt) => {
36-
opt.props.forEach((prop) => {
37-
config.set(opt.name, prop);
38-
});
37+
config.set(opt.name, opt.props);
3938
});
4039
}
4140

@@ -82,7 +81,7 @@ module.exports = {
8281
if (context.options[0]) {
8382
parseLegacyOption(linkComponents, context.options[0]);
8483
}
85-
84+
8685
return {
8786
JSXAttribute(node) {
8887
if (shouldVerifyProp(node, linkComponents) && hasJavaScriptProtocol(node)) {

lib/rules/jsx-no-target-blank.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,16 @@ function attributeValuePossiblyBlank(attribute) {
4848
return false;
4949
}
5050

51-
function hasExternalLink(node, linkAttribute, warnOnSpreadAttributes, spreadAttributeIndex) {
52-
const linkIndex = findLastIndex(node.attributes, (attr) => attr.name && attr.name.name === linkAttribute);
51+
function hasExternalLink(node, linkAttributes, warnOnSpreadAttributes, spreadAttributeIndex) {
52+
const linkIndex = findLastIndex(node.attributes, (attr) => attr.name && linkAttributes.includes(attr.name.name));
5353
const foundExternalLink = linkIndex !== -1 && ((attr) => attr.value && attr.value.type === 'Literal' && /^(?:\w+:|\/\/)/.test(attr.value.value))(
5454
node.attributes[linkIndex]);
5555
return foundExternalLink || (warnOnSpreadAttributes && linkIndex < spreadAttributeIndex);
5656
}
5757

58-
function hasDynamicLink(node, linkAttribute) {
58+
function hasDynamicLink(node, linkAttributes) {
5959
const dynamicLinkIndex = findLastIndex(node.attributes, (attr) => attr.name
60-
&& attr.name.name === linkAttribute
60+
&& linkAttributes.includes(attr.name.name)
6161
&& attr.value
6262
&& attr.value.type === 'JSXExpressionContainer');
6363
if (dynamicLinkIndex !== -1) {
@@ -194,9 +194,9 @@ module.exports = {
194194
}
195195
}
196196

197-
const linkAttribute = linkComponents.get(node.name.name);
198-
const hasDangerousLink = hasExternalLink(node, linkAttribute, warnOnSpreadAttributes, spreadAttributeIndex)
199-
|| (enforceDynamicLinks === 'always' && hasDynamicLink(node, linkAttribute));
197+
const linkAttributes = linkComponents.get(node.name.name);
198+
const hasDangerousLink = hasExternalLink(node, linkAttributes, warnOnSpreadAttributes, spreadAttributeIndex)
199+
|| (enforceDynamicLinks === 'always' && hasDynamicLink(node, linkAttributes));
200200
if (hasDangerousLink && !hasSecureRel(node, allowReferrer, warnOnSpreadAttributes, spreadAttributeIndex)) {
201201
const messageId = allowReferrer ? 'noTargetBlankWithoutNoopener' : 'noTargetBlankWithoutNoreferrer';
202202
const relValue = allowReferrer ? 'noopener' : 'noreferrer';
@@ -265,11 +265,11 @@ module.exports = {
265265
return;
266266
}
267267

268-
const formAttribute = formComponents.get(node.name.name);
268+
const formAttributes = formComponents.get(node.name.name);
269269

270270
if (
271-
hasExternalLink(node, formAttribute)
272-
|| (enforceDynamicLinks === 'always' && hasDynamicLink(node, formAttribute))
271+
hasExternalLink(node, formAttributes)
272+
|| (enforceDynamicLinks === 'always' && hasDynamicLink(node, formAttributes))
273273
) {
274274
const messageId = allowReferrer ? 'noTargetBlankWithoutNoopener' : 'noTargetBlankWithoutNoreferrer';
275275
report(context, messages[messageId], messageId, {

lib/util/linkComponents.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ function getFormComponents(context) {
2424
);
2525
return new Map(map(iterFrom(formComponents), (value) => {
2626
if (typeof value === 'string') {
27-
return [value, DEFAULT_FORM_ATTRIBUTE];
27+
return [value, [DEFAULT_FORM_ATTRIBUTE]];
2828
}
29-
return [value.name, value.formAttribute];
29+
return [value.name, value.formAttributes || [value.formAttribute]];
3030
}));
3131
}
3232

@@ -37,9 +37,9 @@ function getLinkComponents(context) {
3737
);
3838
return new Map(map(iterFrom(linkComponents), (value) => {
3939
if (typeof value === 'string') {
40-
return [value, DEFAULT_LINK_ATTRIBUTE];
40+
return [value, [DEFAULT_LINK_ATTRIBUTE]];
4141
}
42-
return [value.name, value.linkAttribute];
42+
return [value.name, value.linkAttributes || [value.linkAttribute]];
4343
}));
4444
}
4545

tests/util/linkComponents.js

+43-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ describe('linkComponentsFunctions', () => {
88
it('returns a default map of components', () => {
99
const context = {};
1010
assert.deepStrictEqual(linkComponentsUtil.getLinkComponents(context), new Map([
11-
['a', 'href'],
11+
['a', ['href']],
1212
]));
1313
});
1414

@@ -19,16 +19,55 @@ describe('linkComponentsFunctions', () => {
1919
name: 'Link',
2020
linkAttribute: 'to',
2121
},
22+
{
23+
name: 'Link2',
24+
linkAttributes: ['to1', 'to2'],
25+
},
2226
];
2327
const context = {
2428
settings: {
2529
linkComponents,
2630
},
2731
};
2832
assert.deepStrictEqual(linkComponentsUtil.getLinkComponents(context), new Map([
29-
['a', 'href'],
30-
['Hyperlink', 'href'],
31-
['Link', 'to'],
33+
['a', ['href']],
34+
['Hyperlink', ['href']],
35+
['Link', ['to']],
36+
['Link2', ['to1', 'to2']],
37+
]));
38+
});
39+
});
40+
41+
describe('getFormComponents', () => {
42+
it('returns a default map of components', () => {
43+
const context = {};
44+
assert.deepStrictEqual(linkComponentsUtil.getFormComponents(context), new Map([
45+
['form', ['action']],
46+
]));
47+
});
48+
49+
it('returns a map of components', () => {
50+
const formComponents = [
51+
'Form',
52+
{
53+
name: 'MyForm',
54+
linkAttribute: 'endpoint',
55+
},
56+
{
57+
name: 'MyForm2',
58+
linkAttributes: ['endpoint1', 'endpoint2'],
59+
},
60+
];
61+
const context = {
62+
settings: {
63+
formComponents,
64+
},
65+
};
66+
assert.deepStrictEqual(linkComponentsUtil.getFormComponents(context), new Map([
67+
['form', ['action']],
68+
['Form', ['action']],
69+
['MyForm', ['endpoint']],
70+
['MyForm2', ['endpoint1', 'endpoint2']],
3271
]));
3372
});
3473
});

0 commit comments

Comments
 (0)