Skip to content

Commit 1014f8c

Browse files
burtekljharb
authored andcommitted
[New] jsx-no-script-url: add includeFromSettings option to support linkAttributes setting
1 parent de35e6f commit 1014f8c

File tree

4 files changed

+189
-41
lines changed

4 files changed

+189
-41
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
1616
* [`jsx-filename-extension`]: add `ignoreFilesWithoutCode` option to allow empty files ([#3674][] @burtek)
1717
* [`jsx-boolean-value`]: add `assumeUndefinedIsFalse` option ([#3675][] @developer-bandi)
1818
* `linkAttribute` setting, [`jsx-no-target-blank`]: support multiple properties ([#3673][] @burtek)
19+
* [`jsx-no-script-url`]: add `includeFromSettings` option to support `linkAttributes` setting ([#3673][] @burtek)
1920

2021
### Fixed
2122
* [`jsx-no-leaked-render`]: preserve RHS parens for multiline jsx elements while fixing ([#3623][] @akulsr0)

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

Lines changed: 42 additions & 2 deletions
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

Lines changed: 80 additions & 35 deletions
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,84 @@ 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+
},
90+
additionalItems: false,
7591
},
76-
},
92+
],
93+
additionalItems: false,
7794
},
78-
required: ['name', 'props'],
79-
additionalProperties: false,
80-
},
81-
}],
95+
{
96+
type: 'array',
97+
items: [
98+
{
99+
type: 'object',
100+
properties: {
101+
includeFromSettings: {
102+
type: 'boolean',
103+
},
104+
},
105+
additionalItems: false,
106+
},
107+
],
108+
additionalItems: false,
109+
},
110+
],
111+
},
82112
},
83113

84114
create(context) {
85-
const config = context.options[0] || [];
115+
const options = context.options;
116+
const hasLegacyOption = Array.isArray(options[0]);
117+
const legacyOptions = hasLegacyOption ? options[0] : [];
118+
// eslint-disable-next-line no-nested-ternary
119+
const objectOption = (hasLegacyOption && options.length > 1)
120+
? options[1]
121+
: (options.length > 0
122+
? options[0]
123+
: {
124+
includeFromSettings: false,
125+
}
126+
);
127+
const includeFromSettings = objectOption.includeFromSettings;
128+
129+
const linkComponents = linkComponentsUtil.getLinkComponents(includeFromSettings ? context : {});
130+
parseLegacyOption(linkComponents, legacyOptions);
131+
86132
return {
87133
JSXAttribute(node) {
88-
const parent = node.parent;
89-
if (shouldVerifyElement(parent, config) && shouldVerifyProp(node, config) && hasJavaScriptProtocol(node)) {
134+
if (shouldVerifyProp(node, linkComponents) && hasJavaScriptProtocol(node)) {
90135
report(context, messages.noScriptURL, 'noScriptURL', {
91136
node,
92137
});

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

Lines changed: 66 additions & 4 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy