Skip to content

Commit 1d15932

Browse files
committed
[New] jsx-no-script-url: support linkAttributes setting
1 parent b2e744d commit 1d15932

File tree

3 files changed

+184
-41
lines changed

3 files changed

+184
-41
lines changed

docs/rules/jsx-no-script-url.md

+42-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,14 @@ Examples of **correct** code for this rule:
2323
<a href={"javascript:"}></a>
2424
```
2525

26+
This rule takes the `linkComponents` setting into account.
27+
2628
## Rule Options
2729

30+
This rule accepts array option (optional) and object option (optional).
31+
32+
### Array option (default `[]`)
33+
2834
```json
2935
{
3036
"react/jsx-no-script-url": [
@@ -45,11 +51,11 @@ Examples of **correct** code for this rule:
4551

4652
Allows you to indicate a specific list of properties used by a custom component to be checked.
4753

48-
### name
54+
#### name
4955

5056
Component name.
5157

52-
### props
58+
#### props
5359

5460
List of properties that should be validated.
5561

@@ -60,3 +66,37 @@ Examples of **incorrect** code for this rule, when configured with the above opt
6066
<Foo href="javascript:void(0)"></Foo>
6167
<Foo to="javascript:void(0)"></Foo>
6268
```
69+
70+
### Object option
71+
72+
#### includeFromSettings (default `false`)
73+
74+
Indicates if the `linkComponents` config in [global shared settings](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/README.md#configuration) should also be taken into account. If enabled, components and properties defined in settings will be added to the list provided in first option (if provided):
75+
76+
```json
77+
{
78+
"react/jsx-no-script-url": [
79+
"error",
80+
[
81+
{
82+
"name": "Link",
83+
"props": ["to"]
84+
},
85+
{
86+
"name": "Foo",
87+
"props": ["href", "to"]
88+
}
89+
],
90+
{ "includeFromSettings": true }
91+
]
92+
}
93+
```
94+
95+
If only global settings should be used for this rule, the array option can be omitted:
96+
97+
```jsonc
98+
{
99+
// same as ["error", [], { "includeFromSettings": true }]
100+
"react/jsx-no-script-url": ["error", { "includeFromSettings": true }]
101+
}
102+
```

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

+76-35
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
'use strict';
77

8+
const includes = require('array-includes');
89
const docsUrl = require('../util/docsUrl');
10+
const linkComponentsUtil = require('../util/linkComponents');
911
const report = require('../util/report');
1012

1113
// ------------------------------------------------------------------------------
@@ -21,26 +23,20 @@ function hasJavaScriptProtocol(attr) {
2123
&& isJavaScriptProtocol.test(attr.value.value);
2224
}
2325

24-
function shouldVerifyElement(node, config) {
25-
const name = node.name && node.name.name;
26-
return name === 'a' || config.find((i) => i.name === name);
27-
}
28-
2926
function shouldVerifyProp(node, config) {
3027
const name = node.name && node.name.name;
3128
const parentName = node.parent.name && node.parent.name.name;
3229

33-
if (parentName === 'a' && name === 'href') {
34-
return true;
35-
}
30+
if (!name || !parentName || !config.has(parentName)) return false;
3631

37-
const el = config.find((i) => i.name === parentName);
38-
if (!el) {
39-
return false;
40-
}
32+
const attributes = config.get(parentName);
33+
return includes(attributes, name);
34+
}
4135

42-
const props = el.props || [];
43-
return node.name && props.indexOf(name) !== -1;
36+
function parseLegacyOption(config, option) {
37+
option.forEach((opt) => {
38+
config.set(opt.name, opt.props);
39+
});
4440
}
4541

4642
const messages = {
@@ -58,35 +54,80 @@ module.exports = {
5854

5955
messages,
6056

61-
schema: [{
62-
type: 'array',
63-
uniqueItems: true,
64-
items: {
65-
type: 'object',
66-
properties: {
67-
name: {
68-
type: 'string',
69-
},
70-
props: {
71-
type: 'array',
72-
items: {
73-
type: 'string',
57+
schema: {
58+
anyOf: [
59+
{
60+
type: 'array',
61+
items: [
62+
{
63+
type: 'array',
7464
uniqueItems: true,
65+
items: {
66+
type: 'object',
67+
properties: {
68+
name: {
69+
type: 'string',
70+
},
71+
props: {
72+
type: 'array',
73+
items: {
74+
type: 'string',
75+
uniqueItems: true,
76+
},
77+
},
78+
},
79+
required: ['name', 'props'],
80+
additionalProperties: false,
81+
},
82+
},
83+
{
84+
type: 'object',
85+
properties: {
86+
includeFromSettings: {
87+
type: 'boolean',
88+
},
89+
},
7590
},
76-
},
91+
],
92+
additionalItems: false,
7793
},
78-
required: ['name', 'props'],
79-
additionalProperties: false,
80-
},
81-
}],
94+
{
95+
type: 'array',
96+
items: [
97+
{
98+
type: 'object',
99+
properties: {
100+
includeFromSettings: {
101+
type: 'boolean',
102+
},
103+
},
104+
},
105+
],
106+
additionalItems: false,
107+
},
108+
],
109+
},
82110
},
83111

84112
create(context) {
85-
const config = context.options[0] || [];
113+
const options = context.options.slice();
114+
let legacyOptions = [];
115+
let includeFromSettings = false;
116+
117+
if (Array.isArray(options[0])) {
118+
legacyOptions = options.shift();
119+
}
120+
if (typeof options[0] === 'object') {
121+
const objOption = options.shift();
122+
includeFromSettings = objOption.includeFromSettings || includeFromSettings;
123+
}
124+
125+
const linkComponents = linkComponentsUtil.getLinkComponents(includeFromSettings ? context : {});
126+
parseLegacyOption(linkComponents, legacyOptions);
127+
86128
return {
87129
JSXAttribute(node) {
88-
const parent = node.parent;
89-
if (shouldVerifyElement(parent, config) && shouldVerifyProp(node, config) && hasJavaScriptProtocol(node)) {
130+
if (shouldVerifyProp(node, linkComponents) && hasJavaScriptProtocol(node)) {
90131
report(context, messages.noScriptURL, 'noScriptURL', {
91132
node,
92133
});

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

+66-4
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,22 @@ ruleTester.run('jsx-no-script-url', rule, {
3838
{ code: '<a href={"javascript:"}></a>' },
3939
{ code: '<Foo href="javascript:"></Foo>' },
4040
{ code: '<a href />' },
41+
{
42+
code: '<Foo href="javascript:"></Foo>',
43+
settings: {
44+
linkComponents: [{ name: 'Foo', linkAttribute: ['to', 'href'] }],
45+
},
46+
},
47+
{
48+
code: '<Foo href="javascript:"></Foo>',
49+
options: [[], { includeFromSettings: false }],
50+
settings: {
51+
linkComponents: [{ name: 'Foo', linkAttribute: ['to', 'href'] }],
52+
},
53+
},
4154
]),
4255
invalid: parsers.all([
56+
// defaults
4357
{
4458
code: '<a href="javascript:"></a>',
4559
errors: [{ messageId: 'noScriptURL' }],
@@ -52,6 +66,8 @@ ruleTester.run('jsx-no-script-url', rule, {
5266
code: '<a href="j\n\n\na\rv\tascript:"></a>',
5367
errors: [{ messageId: 'noScriptURL' }],
5468
},
69+
70+
// with component passed by options
5571
{
5672
code: '<Foo to="javascript:"></Foo>',
5773
errors: [{ messageId: 'noScriptURL' }],
@@ -66,6 +82,34 @@ ruleTester.run('jsx-no-script-url', rule, {
6682
[{ name: 'Foo', props: ['to', 'href'] }],
6783
],
6884
},
85+
{ // make sure it still uses defaults when passed options
86+
code: '<a href="javascript:void(0)"></a>',
87+
errors: [{ messageId: 'noScriptURL' }],
88+
options: [
89+
[{ name: 'Foo', props: ['to', 'href'] }],
90+
],
91+
},
92+
93+
// with components passed by settings
94+
{
95+
code: '<Foo to="javascript:"></Foo>',
96+
errors: [{ messageId: 'noScriptURL' }],
97+
options: [
98+
[{ name: 'Bar', props: ['to', 'href'] }],
99+
{ includeFromSettings: true },
100+
],
101+
settings: {
102+
linkComponents: [{ name: 'Foo', linkAttribute: 'to' }],
103+
},
104+
},
105+
{
106+
code: '<Foo href="javascript:"></Foo>',
107+
errors: [{ messageId: 'noScriptURL' }],
108+
options: [{ includeFromSettings: true }],
109+
settings: {
110+
linkComponents: [{ name: 'Foo', linkAttribute: ['to', 'href'] }],
111+
},
112+
},
69113
{
70114
code: `
71115
<div>
@@ -78,11 +122,29 @@ ruleTester.run('jsx-no-script-url', rule, {
78122
{ messageId: 'noScriptURL' },
79123
],
80124
options: [
81-
[
82-
{ name: 'Foo', props: ['to', 'href'] },
83-
{ name: 'Bar', props: ['link'] },
84-
],
125+
[{ name: 'Bar', props: ['link'] }],
126+
{ includeFromSettings: true },
127+
],
128+
settings: {
129+
linkComponents: [{ name: 'Foo', linkAttribute: ['to', 'href'] }],
130+
},
131+
},
132+
{
133+
code: `
134+
<div>
135+
<Foo href="javascript:"></Foo>
136+
<Bar link="javascript:"></Bar>
137+
</div>
138+
`,
139+
errors: [
140+
{ messageId: 'noScriptURL' },
141+
],
142+
options: [
143+
[{ name: 'Bar', props: ['link'] }],
85144
],
145+
settings: {
146+
linkComponents: [{ name: 'Foo', linkAttribute: ['to', 'href'] }],
147+
},
86148
},
87149
]),
88150
});

0 commit comments

Comments
 (0)