eslint-plugin-formatjs 6.4.4 → 6.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/index.d.ts +15 -12
  2. package/index.js +4603 -54
  3. package/index.js.map +1 -0
  4. package/package.json +3 -3
  5. package/util.d.ts +654 -29
  6. package/util.js +62 -134
  7. package/util.js.map +1 -0
  8. package/emoji-data.generated.d.ts +0 -27
  9. package/emoji-data.generated.js +0 -2564
  10. package/emoji-utils.d.ts +0 -43
  11. package/emoji-utils.js +0 -145
  12. package/messages.d.ts +0 -2
  13. package/messages.js +0 -1
  14. package/rules/blocklist-elements.d.ts +0 -14
  15. package/rules/blocklist-elements.js +0 -129
  16. package/rules/enforce-default-message.d.ts +0 -7
  17. package/rules/enforce-default-message.js +0 -57
  18. package/rules/enforce-description.d.ts +0 -11
  19. package/rules/enforce-description.js +0 -97
  20. package/rules/enforce-id.d.ts +0 -8
  21. package/rules/enforce-id.js +0 -135
  22. package/rules/enforce-placeholders.d.ts +0 -3
  23. package/rules/enforce-placeholders.js +0 -128
  24. package/rules/enforce-plural-rules.d.ts +0 -14
  25. package/rules/enforce-plural-rules.js +0 -108
  26. package/rules/no-camel-case.d.ts +0 -3
  27. package/rules/no-camel-case.js +0 -85
  28. package/rules/no-complex-selectors.d.ts +0 -3
  29. package/rules/no-complex-selectors.js +0 -119
  30. package/rules/no-emoji.d.ts +0 -8
  31. package/rules/no-emoji.js +0 -88
  32. package/rules/no-id.d.ts +0 -3
  33. package/rules/no-id.js +0 -48
  34. package/rules/no-invalid-icu.d.ts +0 -3
  35. package/rules/no-invalid-icu.js +0 -56
  36. package/rules/no-literal-string-in-jsx.d.ts +0 -3
  37. package/rules/no-literal-string-in-jsx.js +0 -161
  38. package/rules/no-literal-string-in-object.d.ts +0 -3
  39. package/rules/no-literal-string-in-object.js +0 -59
  40. package/rules/no-missing-icu-plural-one-placeholders.d.ts +0 -5
  41. package/rules/no-missing-icu-plural-one-placeholders.js +0 -94
  42. package/rules/no-multiple-plurals.d.ts +0 -3
  43. package/rules/no-multiple-plurals.js +0 -76
  44. package/rules/no-multiple-whitespaces.d.ts +0 -3
  45. package/rules/no-multiple-whitespaces.js +0 -126
  46. package/rules/no-offset.d.ts +0 -3
  47. package/rules/no-offset.js +0 -75
  48. package/rules/no-useless-message.d.ts +0 -3
  49. package/rules/no-useless-message.js +0 -69
  50. package/rules/prefer-formatted-message.d.ts +0 -3
  51. package/rules/prefer-formatted-message.js +0 -26
  52. package/rules/prefer-full-sentence.d.ts +0 -3
  53. package/rules/prefer-full-sentence.js +0 -111
  54. package/rules/prefer-pound-in-plural.d.ts +0 -3
  55. package/rules/prefer-pound-in-plural.js +0 -163
@@ -1,3 +0,0 @@
1
- import type { Rule } from "eslint";
2
- export declare const name = "prefer-formatted-message";
3
- export declare const rule: Rule.RuleModule;
@@ -1,26 +0,0 @@
1
- import { isIntlFormatMessageCall } from "../util.js";
2
- export const name = "prefer-formatted-message";
3
- export const rule = {
4
- meta: {
5
- type: "suggestion",
6
- docs: {
7
- description: "Prefer `FormattedMessage` component over `intl.formatMessage` if applicable.",
8
- url: "https://formatjs.github.io/docs/tooling/linter#prefer-formatted-message"
9
- },
10
- messages: { jsxChildren: "Prefer `FormattedMessage` over `intl.formatMessage` in the JSX children expression." },
11
- schema: []
12
- },
13
- create(context) {
14
- return { JSXElement: (node) => {
15
- node.children.forEach((child) => {
16
- if (child.type !== "JSXExpressionContainer" || !isIntlFormatMessageCall(child.expression)) {
17
- return;
18
- }
19
- context.report({
20
- node: child,
21
- messageId: "jsxChildren"
22
- });
23
- });
24
- } };
25
- }
26
- };
@@ -1,3 +0,0 @@
1
- import type { Rule } from "eslint";
2
- export declare const name = "prefer-full-sentence";
3
- export declare const rule: Rule.RuleModule;
@@ -1,111 +0,0 @@
1
- import { parse, TYPE } from "@formatjs/icu-messageformat-parser";
2
- import { extractMessages, getSettings } from "../util.js";
3
- import { CORE_MESSAGES } from "../messages.js";
4
- /**
5
- * Get the first boundary element, recursing into tags since
6
- * <b>hello</b> starts with the literal "hello".
7
- */
8
- function getFirstBoundaryElement(ast) {
9
- if (ast.length === 0) return null;
10
- const first = ast[0];
11
- if (first.type === TYPE.tag) {
12
- return getFirstBoundaryElement(first.children);
13
- }
14
- return first;
15
- }
16
- /**
17
- * Get the last boundary element, recursing into tags.
18
- */
19
- function getLastBoundaryElement(ast) {
20
- if (ast.length === 0) return null;
21
- const last = ast[ast.length - 1];
22
- if (last.type === TYPE.tag) {
23
- return getLastBoundaryElement(last.children);
24
- }
25
- return last;
26
- }
27
- function findWhitespaceIssues(ast) {
28
- const issues = [];
29
- const first = getFirstBoundaryElement(ast);
30
- const last = getLastBoundaryElement(ast);
31
- // Ignore messages that are only whitespace (edge case)
32
- if (first === last && first?.type === TYPE.literal && ast.length === 1 && first.value.trim() === "") {
33
- return issues;
34
- }
35
- if (first?.type === TYPE.literal && /^\s/.test(first.value)) {
36
- issues.push("leadingWhitespace");
37
- }
38
- if (last?.type === TYPE.literal && /\s$/.test(last.value)) {
39
- issues.push("trailingWhitespace");
40
- }
41
- // Check each plural/select option branch independently
42
- for (const element of ast) {
43
- switch (element.type) {
44
- case TYPE.plural:
45
- case TYPE.select: {
46
- for (const option of Object.values(element.options)) {
47
- issues.push(...findWhitespaceIssues(option.value));
48
- }
49
- break;
50
- }
51
- }
52
- }
53
- return issues;
54
- }
55
- function checkNode(context, node) {
56
- const msgs = extractMessages(node, getSettings(context));
57
- for (const [{ message: { defaultMessage }, messageNode }] of msgs) {
58
- if (!defaultMessage || !messageNode) {
59
- continue;
60
- }
61
- let ast;
62
- try {
63
- ast = parse(defaultMessage);
64
- } catch (e) {
65
- context.report({
66
- node: messageNode,
67
- messageId: "parseError",
68
- data: { error: e instanceof Error ? e.message : String(e) }
69
- });
70
- return;
71
- }
72
- const issues = findWhitespaceIssues(ast);
73
- // Deduplicate
74
- const seen = new Set();
75
- for (const issue of issues) {
76
- if (seen.has(issue)) continue;
77
- seen.add(issue);
78
- context.report({
79
- node: messageNode,
80
- messageId: issue
81
- });
82
- }
83
- }
84
- }
85
- export const name = "prefer-full-sentence";
86
- export const rule = {
87
- meta: {
88
- type: "suggestion",
89
- docs: {
90
- description: "Detects messages with leading/trailing whitespace, which suggests string concatenation instead of full sentences",
91
- url: "https://formatjs.github.io/docs/tooling/linter#prefer-full-sentence"
92
- },
93
- messages: {
94
- ...CORE_MESSAGES,
95
- leadingWhitespace: "Messages should be full sentences — leading whitespace suggests string concatenation",
96
- trailingWhitespace: "Messages should be full sentences — trailing whitespace suggests string concatenation"
97
- },
98
- schema: []
99
- },
100
- create(context) {
101
- const callExpressionVisitor = (node) => checkNode(context, node);
102
- const parserServices = context.sourceCode.parserServices;
103
- if (parserServices?.defineTemplateBodyVisitor) {
104
- return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
105
- }
106
- return {
107
- JSXOpeningElement: (node) => checkNode(context, node),
108
- CallExpression: callExpressionVisitor
109
- };
110
- }
111
- };
@@ -1,3 +0,0 @@
1
- import type { Rule } from "eslint";
2
- export declare const name = "prefer-pound-in-plural";
3
- export declare const rule: Rule.RuleModule;
@@ -1,163 +0,0 @@
1
- import { parse, TYPE } from "@formatjs/icu-messageformat-parser";
2
- import MagicString from "magic-string";
3
- import { extractMessages, getSettings, patchMessage } from "../util.js";
4
- import { CORE_MESSAGES } from "../messages.js";
5
- function verifyAst(context, messageNode, ast) {
6
- const patches = [];
7
- _verifyAst(ast);
8
- if (patches.length > 0) {
9
- const patchedMessage = patchMessage(messageNode, ast, (content) => {
10
- return patches.reduce((magicString, patch) => {
11
- switch (patch.type) {
12
- case "prependLeft": return magicString.prependLeft(patch.index, patch.content);
13
- case "remove": return magicString.remove(patch.start, patch.end);
14
- case "update": return magicString.update(patch.start, patch.end, patch.content);
15
- }
16
- }, new MagicString(content)).toString();
17
- });
18
- context.report({
19
- node: messageNode,
20
- messageId: "preferPoundInPlurals",
21
- fix: patchedMessage !== null ? (fixer) => fixer.replaceText(messageNode, patchedMessage) : null
22
- });
23
- }
24
- function _verifyAst(ast) {
25
- for (let i = 0; i < ast.length; i++) {
26
- const current = ast[i];
27
- switch (current.type) {
28
- case TYPE.argument:
29
- case TYPE.number: {
30
- // Applicable to only plain argument or number argument without any style
31
- if (current.type === TYPE.number && current.style) {
32
- break;
33
- }
34
- const next = ast[i + 1];
35
- const nextNext = ast[i + 2];
36
- if (next && nextNext && next.type === TYPE.literal && next.value === " " && nextNext.type === TYPE.plural && nextNext.value === current.value) {
37
- // `{A} {A, plural, one {B} other {Bs}}` => `{A, plural, one {# B} other {# Bs}}`
38
- _removeRangeAndPrependPluralClauses(current.location.start.offset, next.location.end.offset, nextNext, "# ");
39
- } else if (next && next.type === TYPE.plural && next.value === current.value) {
40
- // `{A}{A, plural, one {B} other {Bs}}` => `{A, plural, one {#B} other {#Bs}}`
41
- _removeRangeAndPrependPluralClauses(current.location.start.offset, current.location.end.offset, next, "#");
42
- }
43
- break;
44
- }
45
- case TYPE.plural: {
46
- // `{A, plural, one {{A} B} other {{A} Bs}}` => `{A, plural, one {# B} other {# Bs}}`
47
- const name = current.value;
48
- for (const { value } of Object.values(current.options)) {
49
- _replacementArgumentWithPound(name, value);
50
- }
51
- break;
52
- }
53
- case TYPE.select: {
54
- for (const { value } of Object.values(current.options)) {
55
- _verifyAst(value);
56
- }
57
- break;
58
- }
59
- case TYPE.tag:
60
- _verifyAst(current.children);
61
- break;
62
- default: break;
63
- }
64
- }
65
- }
66
- // Replace plain argument of number argument w/o style option that matches
67
- // the name with a pound sign.
68
- function _replacementArgumentWithPound(name, ast) {
69
- for (const element of ast) {
70
- switch (element.type) {
71
- case TYPE.argument:
72
- case TYPE.number: {
73
- if (element.value === name && (element.type !== TYPE.number || !element.style)) {
74
- patches.push({
75
- type: "update",
76
- start: element.location.start.offset,
77
- end: element.location.end.offset,
78
- content: "#"
79
- });
80
- }
81
- break;
82
- }
83
- case TYPE.tag: {
84
- _replacementArgumentWithPound(name, element.children);
85
- break;
86
- }
87
- case TYPE.select: {
88
- for (const { value } of Object.values(element.options)) {
89
- _replacementArgumentWithPound(name, value);
90
- }
91
- break;
92
- }
93
- default: break;
94
- }
95
- }
96
- }
97
- // Helper to remove a certain text range and then prepend the specified text to
98
- // each plural clause.
99
- function _removeRangeAndPrependPluralClauses(rangeToRemoveStart, rangeToRemoveEnd, pluralElement, prependText) {
100
- // Delete both the `{A}` and ` `
101
- patches.push({
102
- type: "remove",
103
- start: rangeToRemoveStart,
104
- end: rangeToRemoveEnd
105
- });
106
- // Insert `# ` to the beginning of every option clause
107
- for (const { location } of Object.values(pluralElement.options)) {
108
- // location marks the entire clause with the surrounding braces
109
- patches.push({
110
- type: "prependLeft",
111
- index: location.start.offset + 1,
112
- content: prependText
113
- });
114
- }
115
- }
116
- }
117
- function checkNode(context, node) {
118
- const msgs = extractMessages(node, getSettings(context));
119
- for (const [{ message: { defaultMessage }, messageNode }] of msgs) {
120
- if (!defaultMessage || !messageNode) {
121
- continue;
122
- }
123
- let ast;
124
- try {
125
- ast = parse(defaultMessage, { captureLocation: true });
126
- } catch (e) {
127
- context.report({
128
- node: messageNode,
129
- messageId: "parseError",
130
- data: { error: e instanceof Error ? e.message : String(e) }
131
- });
132
- return;
133
- }
134
- verifyAst(context, messageNode, ast);
135
- }
136
- }
137
- export const name = "prefer-pound-in-plural";
138
- export const rule = {
139
- meta: {
140
- type: "suggestion",
141
- docs: {
142
- description: "Prefer using # to reference the count in the plural argument.",
143
- url: "https://formatjs.github.io/docs/tooling/linter#prefer-pound-in-plurals"
144
- },
145
- messages: {
146
- ...CORE_MESSAGES,
147
- preferPoundInPlurals: "Prefer using # to reference the count in the plural argument instead of repeating the argument."
148
- },
149
- fixable: "code",
150
- schema: []
151
- },
152
- create(context) {
153
- const callExpressionVisitor = (node) => checkNode(context, node);
154
- const parserServices = context.sourceCode.parserServices;
155
- if (parserServices?.defineTemplateBodyVisitor) {
156
- return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
157
- }
158
- return {
159
- JSXOpeningElement: (node) => checkNode(context, node),
160
- CallExpression: callExpressionVisitor
161
- };
162
- }
163
- };