Skip to content

Commit 03c540b

Browse files
authored
Add require-post-message-target-origin rule (#1326)
1 parent cec7f11 commit 03c540b

11 files changed

+336
-6
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Enforce using the `targetOrigin` argument with `window.postMessage()`
2+
3+
When calling [`window.postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) without the `targetOrigin` argument, the message cannot be received by any window.
4+
5+
## Fail
6+
7+
```js
8+
window.postMessage(message);
9+
```
10+
11+
## Pass
12+
13+
```js
14+
window.postMessage(message, 'https://example.com');
15+
```
16+
17+
```js
18+
window.postMessage(message, '*');
19+
```

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ module.exports = {
123123
'unicorn/prevent-abbreviations': 'error',
124124
'unicorn/require-array-join-separator': 'error',
125125
'unicorn/require-number-to-fixed-digits-argument': 'error',
126+
'unicorn/require-post-message-target-origin': 'error',
126127
'unicorn/string-content': 'off',
127128
'unicorn/throw-new-error': 'error'
128129
}

readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ Configure it in `package.json`.
116116
"unicorn/prevent-abbreviations": "error",
117117
"unicorn/require-array-join-separator": "error",
118118
"unicorn/require-number-to-fixed-digits-argument": "error",
119+
"unicorn/require-post-message-target-origin": "error",
119120
"unicorn/string-content": "off",
120121
"unicorn/throw-new-error": "error"
121122
}
@@ -217,6 +218,7 @@ Each rule has emojis denoting:
217218
| [prevent-abbreviations](docs/rules/prevent-abbreviations.md) | Prevent abbreviations. || 🔧 | |
218219
| [require-array-join-separator](docs/rules/require-array-join-separator.md) | Enforce using the separator argument with `Array#join()`. || 🔧 | |
219220
| [require-number-to-fixed-digits-argument](docs/rules/require-number-to-fixed-digits-argument.md) | Enforce using the digits argument with `Number#toFixed()`. || 🔧 | |
221+
| [require-post-message-target-origin](docs/rules/require-post-message-target-origin.md) | Enforce using the `targetOrigin` argument with `window.postMessage()`. || | 💡 |
220222
| [string-content](docs/rules/string-content.md) | Enforce better string content. | | 🔧 | 💡 |
221223
| [throw-new-error](docs/rules/throw-new-error.md) | Require `new` when throwing an error. || 🔧 | |
222224

rules/fix/append-argument.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ function appendArgument(fixer, node, text, sourceCode) {
1111

1212
const [penultimateToken, lastToken] = sourceCode.getLastTokens(node, 2);
1313
if (node.arguments.length > 0) {
14-
text = isCommaToken(penultimateToken) ? `${text},` : `, ${text}`;
14+
text = isCommaToken(penultimateToken) ? ` ${text},` : `, ${text}`;
1515
}
1616

1717
return fixer.insertTextBefore(lastToken, text);
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
'use strict';
2+
const getDocumentationUrl = require('./utils/get-documentation-url');
3+
const {methodCallSelector} = require('./selectors');
4+
const {appendArgument} = require('./fix');
5+
6+
const ERROR = 'error';
7+
const SUGGESTION_TARGET_LOCATION_ORIGIN = 'target-location-origin';
8+
const SUGGESTION_SELF_LOCATION_ORIGIN = 'self-location-origin';
9+
const SUGGESTION_STAR = 'star';
10+
const messages = {
11+
[ERROR]: 'Missing the `targetOrigin` argument.',
12+
[SUGGESTION_TARGET_LOCATION_ORIGIN]: 'Use `{{target}}.location.origin`.',
13+
[SUGGESTION_SELF_LOCATION_ORIGIN]: 'Use `self.location.origin`.',
14+
[SUGGESTION_STAR]: 'Use `"*"`.'
15+
};
16+
17+
/** @param {import('eslint').Rule.RuleContext} context */
18+
function create(context) {
19+
const sourceCode = context.getSourceCode();
20+
return {
21+
[methodCallSelector({name: 'postMessage', length: 1})](node) {
22+
const [penultimateToken, lastToken] = sourceCode.getLastTokens(node, 2);
23+
const suggestions = [];
24+
const target = node.callee.object;
25+
if (target.type === 'Identifier') {
26+
const {name} = target;
27+
28+
suggestions.push({
29+
messageId: SUGGESTION_TARGET_LOCATION_ORIGIN,
30+
data: {target: name},
31+
code: `${target.name}.location.origin`
32+
});
33+
34+
if (name !== 'self' && name !== 'window' && name !== 'globalThis') {
35+
suggestions.push({messageId: SUGGESTION_SELF_LOCATION_ORIGIN, code: 'self.location.origin'});
36+
}
37+
} else {
38+
suggestions.push({messageId: SUGGESTION_SELF_LOCATION_ORIGIN, code: 'self.location.origin'});
39+
}
40+
41+
suggestions.push({messageId: SUGGESTION_STAR, code: '\'*\''});
42+
43+
context.report({
44+
loc: {
45+
start: penultimateToken.loc.end,
46+
end: lastToken.loc.end
47+
},
48+
messageId: ERROR,
49+
suggest: suggestions.map(({messageId, data, code}) => ({
50+
messageId,
51+
data,
52+
/** @param {import('eslint').Rule.RuleFixer} fixer */
53+
fix: fixer => appendArgument(fixer, node, code, sourceCode)
54+
}))
55+
});
56+
}
57+
};
58+
}
59+
60+
module.exports = {
61+
create,
62+
meta: {
63+
type: 'problem',
64+
docs: {
65+
description: 'Enforce using the `targetOrigin` argument with `window.postMessage()`.',
66+
url: getDocumentationUrl(__filename),
67+
suggestion: true
68+
},
69+
schema: [],
70+
messages
71+
}
72+
};

scripts/create-rule.mjs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ function updateIndex(id) {
117117
},
118118
{
119119
message: 'No',
120-
value: ''
120+
value: false
121121
}
122122
]
123123
},
@@ -133,6 +133,10 @@ function updateIndex(id) {
133133
}
134134
]);
135135

136+
if (data.fixableType === 'No') {
137+
data.fixableType = false;
138+
}
139+
136140
const {id} = data;
137141

138142
checkFiles(id);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {getTester} from './utils/test.mjs';
2+
3+
const {test} = getTester(import.meta);
4+
5+
test.snapshot({
6+
valid: [
7+
'window.postMessage(message, targetOrigin)',
8+
'postMessage(message)',
9+
'window.postMessage',
10+
'window.postMessage()',
11+
'window.postMessage(message, targetOrigin, transfer)',
12+
'window.postMessage(...message)',
13+
'window[postMessage](message)',
14+
'window["postMessage"](message)',
15+
'window.notPostMessage(message)',
16+
'window.postMessage?.(message)',
17+
'window?.postMessage(message)'
18+
],
19+
invalid: [
20+
'window.postMessage(message)',
21+
'self.postMessage(message)',
22+
'globalThis.postMessage(message)',
23+
'foo.postMessage(message )',
24+
'foo.postMessage( ((message)) )',
25+
'foo.postMessage(message,)',
26+
'foo.postMessage(message , )',
27+
'foo.window.postMessage(message)',
28+
'document.defaultView.postMessage(message)',
29+
'getWindow().postMessage(message)'
30+
]
31+
});

test/snapshots/require-array-join-separator.mjs.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Generated by [AVA](https://avajs.dev).
4242
> Output
4343
4444
`␊
45-
1 | [].join.call(foo,',',)␊
45+
1 | [].join.call(foo, ',',)␊
4646
`
4747

4848
> Error 1/1
@@ -58,7 +58,7 @@ Generated by [AVA](https://avajs.dev).
5858
> Output
5959
6060
`␊
61-
1 | [].join.call(foo , ',',);␊
61+
1 | [].join.call(foo , ',',);␊
6262
`
6363

6464
> Error 1/1
@@ -90,7 +90,7 @@ Generated by [AVA](https://avajs.dev).
9090
> Output
9191
9292
`␊
93-
1 | Array.prototype.join.call(foo, ',',)␊
93+
1 | Array.prototype.join.call(foo, ',',)␊
9494
`
9595

9696
> Error 1/1
@@ -155,7 +155,7 @@ Generated by [AVA](https://avajs.dev).
155155
22 | /**/␊
156156
23 | ,␊
157157
24 | /**/␊
158-
25 | ',',)/**/␊
158+
25 | ',',)/**/␊
159159
26 | )␊
160160
`
161161

1 Byte
Binary file not shown.

0 commit comments

Comments
 (0)