Skip to content
This repository was archived by the owner on May 4, 2020. It is now read-only.

Commit 27222ab

Browse files
author
Long Ho
committed
feat(eslint-plugin-formatjs): add no-multiple-whitespaces rule
This prevents usage of multiple consecutive whitespaces in message. - Consecutive whitespaces are handled differently in different locales. - Prevents `\` linebreaks in JS string which results in awkward whitespaces. ```tsx import {defineMessages} from 'react-intl'; const messages = defineMessages({ // WORKS foo: { defaultMessage: 'Smileys & People', }, // FAILS bar: { defaultMessage: 'Smileys & People', }, // FAILS baz: { defaultMessage: 'this message is too long \ so I wanna line break it.', }, }); ```
1 parent 7089368 commit 27222ab

File tree

4 files changed

+148
-0
lines changed

4 files changed

+148
-0
lines changed

packages/eslint-plugin-formatjs/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,35 @@ const messages = defineMessages({
283283
});
284284
```
285285

286+
## `no-multiple-whitespaces`
287+
288+
This prevents usage of multiple consecutive whitespaces in message.
289+
290+
#### Why
291+
292+
- Consecutive whitespaces are handled differently in different locales.
293+
- Prevents `\` linebreaks in JS string which results in awkward whitespaces.
294+
295+
```tsx
296+
import {defineMessages} from 'react-intl';
297+
298+
const messages = defineMessages({
299+
// WORKS
300+
foo: {
301+
defaultMessage: 'Smileys & People',
302+
},
303+
// FAILS
304+
bar: {
305+
defaultMessage: 'Smileys & People',
306+
},
307+
// FAILS
308+
baz: {
309+
defaultMessage: 'this message is too long \
310+
so I wanna line break it.',
311+
},
312+
});
313+
```
314+
286315
### `no-multiple-plurals`
287316

288317
This prevents specifying multiple plurals in your message.

packages/eslint-plugin-formatjs/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import blacklistElements from './rules/blacklist-elements';
88
import enforcePluralRules from './rules/enforce-plural-rules';
99
import enforcePlaceholders from './rules/enforce-placeholders';
1010
import enforceSupportedDateTimeSkeleton from './rules/supported-datetime-skeleton';
11+
import noMultipleWhitespaces from './rules/no-multiple-whitespaces';
1112
const plugin = {
1213
rules: {
1314
'enforce-description': enforceDescription,
@@ -20,6 +21,7 @@ const plugin = {
2021
'enforce-plural-rules': enforcePluralRules,
2122
'enforce-placeholders': enforcePlaceholders,
2223
'supported-datetime-skeleton': enforceSupportedDateTimeSkeleton,
24+
'no-multiple-whitespaces': noMultipleWhitespaces,
2325
},
2426
};
2527

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import {Rule, Scope} from 'eslint';
2+
import {ImportDeclaration, Node} from 'estree';
3+
import {extractMessages} from '../util';
4+
const MULTIPLE_SPACES = /\s{2,}/g;
5+
6+
function checkNode(
7+
context: Rule.RuleContext,
8+
node: Node,
9+
importedMacroVars: Scope.Variable[]
10+
) {
11+
const msgs = extractMessages(node, importedMacroVars);
12+
13+
for (const [
14+
{
15+
message: {defaultMessage},
16+
messageNode,
17+
},
18+
] of msgs) {
19+
if (!defaultMessage || !messageNode) {
20+
continue;
21+
}
22+
let reportObject: Parameters<typeof context['report']>[0] | undefined;
23+
if (MULTIPLE_SPACES.test(defaultMessage)) {
24+
reportObject = {
25+
node: messageNode,
26+
message: 'Multiple consecutive whitespaces are not allowed',
27+
};
28+
if (messageNode.type === 'Literal' && messageNode.raw) {
29+
reportObject.fix = function(fixer) {
30+
return fixer.replaceText(
31+
messageNode,
32+
messageNode.raw!.replace(MULTIPLE_SPACES, ' ')
33+
);
34+
};
35+
}
36+
if (reportObject) {
37+
context.report(reportObject);
38+
}
39+
}
40+
}
41+
}
42+
43+
const rule: Rule.RuleModule = {
44+
meta: {
45+
type: 'problem',
46+
docs: {
47+
description: 'Disallow emojis in message',
48+
category: 'Errors',
49+
recommended: false,
50+
url:
51+
'https://github.com/formatjs/formatjs/tree/master/packages/eslint-plugin-formatjs#no-emoji',
52+
},
53+
fixable: 'whitespace',
54+
},
55+
create(context) {
56+
let importedMacroVars: Scope.Variable[] = [];
57+
return {
58+
ImportDeclaration: node => {
59+
const moduleName = (node as ImportDeclaration).source.value;
60+
if (moduleName === '@formatjs/macro' || moduleName === 'react-intl') {
61+
importedMacroVars = context.getDeclaredVariables(node);
62+
}
63+
},
64+
JSXOpeningElement: (node: Node) =>
65+
checkNode(context, node, importedMacroVars),
66+
CallExpression: node => checkNode(context, node, importedMacroVars),
67+
};
68+
},
69+
};
70+
71+
export default rule;

packages/eslint-plugin-formatjs/tests/index.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,52 @@ _({
198198
],
199199
});
200200

201+
ruleTester.run('no-multiple-whitespaces', rules['no-multiple-whitespaces'], {
202+
valid: [
203+
`import {_} from '@formatjs/macro'
204+
_({
205+
defaultMessage: 'a {placeholder}',
206+
description: 'asd'
207+
})`,
208+
dynamicMessage,
209+
noMatch,
210+
spreadJsx,
211+
emptyFnCall,
212+
],
213+
invalid: [
214+
{
215+
code:
216+
"import {_} from '@formatjs/macro';_({defaultMessage: 'a \
217+
{placeHolder}'})",
218+
errors: [
219+
{
220+
message: 'Multiple consecutive whitespaces are not allowed',
221+
},
222+
],
223+
},
224+
{
225+
code: "<FormattedMessage defaultMessage='a thing'/>",
226+
errors: [
227+
{
228+
message: 'Multiple consecutive whitespaces are not allowed',
229+
},
230+
],
231+
},
232+
{
233+
code: `
234+
import {_} from '@formatjs/macro'
235+
_({
236+
defaultMessage: 'a {placeHolder}'
237+
})`,
238+
errors: [
239+
{
240+
message: 'Multiple consecutive whitespaces are not allowed',
241+
},
242+
],
243+
},
244+
],
245+
});
246+
201247
ruleTester.run('no-multiple-plurals', rules['no-multiple-plurals'], {
202248
valid: [
203249
`import {_} from '@formatjs/macro'

0 commit comments

Comments
 (0)