eslint-plugin-formatjs 6.4.4 β†’ 6.4.6

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
package/emoji-utils.d.ts DELETED
@@ -1,43 +0,0 @@
1
- /**
2
- * Emoji detection utilities using Intl.Segmenter
3
- */
4
- import { type EmojiVersion } from "./emoji-data.generated.js";
5
- export type { EmojiVersion } from "./emoji-data.generated.js";
6
- /**
7
- * Check if a string contains any emoji
8
- * Uses Unicode 17.0.0 Emoji_Presentation data and variation selector (U+FE0F)
9
- * to properly detect emoji while avoiding false positives from characters like
10
- * #, *, digits 0-9, and text symbols like Β© which have \p{Emoji} but are not visual emoji
11
- */
12
- export declare function hasEmoji(text: string): boolean;
13
- /**
14
- * Extract all emoji from a string using Intl.Segmenter
15
- * Returns an array of emoji characters (including multi-codepoint sequences)
16
- *
17
- * Uses grapheme segmentation to properly handle:
18
- * - Emoji with skin tone modifiers (πŸ™‹πŸ»)
19
- * - ZWJ sequences (πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦)
20
- * - Flag sequences (πŸ‡ΊπŸ‡Έ)
21
- * - Regional indicator sequences
22
- * - Any other complex emoji grapheme clusters
23
- */
24
- export declare function extractEmojis(text: string): string[];
25
- /**
26
- * Check if a version string is a valid emoji version
27
- */
28
- export declare function isValidEmojiVersion(version: string): boolean;
29
- /**
30
- * Get the Unicode version for a given emoji character
31
- * Returns undefined if the emoji is not found in our data
32
- */
33
- export declare function getEmojiVersion(emoji: string): EmojiVersion | undefined;
34
- /**
35
- * Filter function for emoji versions
36
- * Returns a filter function that checks if an emoji is from version or below
37
- */
38
- export declare function filterEmojis(version: EmojiVersion): (emoji: string) => boolean;
39
- /**
40
- * Get all emoji that match the filter
41
- * Generates emoji from codepoint ranges and filters them
42
- */
43
- export declare function getAllEmojis(filter: (emoji: string) => boolean): string[];
package/emoji-utils.js DELETED
@@ -1,145 +0,0 @@
1
- /**
2
- * Emoji detection utilities using Intl.Segmenter
3
- */
4
- import { EMOJI_VERSIONS, EMOJI_RANGES } from "./emoji-data.generated.js";
5
- import * as emojiPresentationRegex from "@unicode/unicode-17.0.0/Binary_Property/Emoji_Presentation/regex.js";
6
- import * as emojiPropertyRegex from "@unicode/unicode-17.0.0/Binary_Property/Emoji/regex.js";
7
- /**
8
- * Cached Intl.Segmenter instance for emoji extraction
9
- * Reused across all extractEmojis calls for better performance
10
- */
11
- const graphemeSegmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
12
- /**
13
- * Regex for detecting emoji using Unicode 17.0.0 Emoji_Presentation data
14
- * Generated from @unicode/unicode-17.0.0/Binary_Property/Emoji_Presentation
15
- * This avoids false positives from #, *, digits, and text symbols like Β©
16
- */
17
- const emojiPresentationRegexCompiled = emojiPresentationRegex.default ?? emojiPresentationRegex;
18
- /**
19
- * Regex for detecting emoji-capable characters using Unicode 17.0.0 Emoji property
20
- * This includes characters that can have emoji presentation (like ❀, β˜€)
21
- */
22
- const emojiPropertyRegexCompiled = emojiPropertyRegex.default ?? emojiPropertyRegex;
23
- /**
24
- * Check if a string contains any emoji
25
- * Uses Unicode 17.0.0 Emoji_Presentation data and variation selector (U+FE0F)
26
- * to properly detect emoji while avoiding false positives from characters like
27
- * #, *, digits 0-9, and text symbols like Β© which have \p{Emoji} but are not visual emoji
28
- */
29
- export function hasEmoji(text) {
30
- // First check for Emoji_Presentation characters (always displayed as emoji)
31
- if (emojiPresentationRegexCompiled.test(text)) {
32
- return true;
33
- }
34
- // Check for variation selector (U+FE0F) which indicates emoji presentation
35
- // But only if it follows an emoji-capable character to avoid false positives
36
- // like "Hello\uFE0F" or "A\uFE0F"
37
- if (text.includes("️")) {
38
- const segments = graphemeSegmenter.segment(text);
39
- for (const { segment } of segments) {
40
- if (segment.includes("️")) {
41
- // Has VS, check if the character before it has the Emoji property
42
- const beforeVS = segment.replace(/\uFE0F/g, "");
43
- if (emojiPropertyRegexCompiled.test(beforeVS)) {
44
- return true;
45
- }
46
- }
47
- }
48
- }
49
- return false;
50
- }
51
- /**
52
- * Extract all emoji from a string using Intl.Segmenter
53
- * Returns an array of emoji characters (including multi-codepoint sequences)
54
- *
55
- * Uses grapheme segmentation to properly handle:
56
- * - Emoji with skin tone modifiers (πŸ™‹πŸ»)
57
- * - ZWJ sequences (πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦)
58
- * - Flag sequences (πŸ‡ΊπŸ‡Έ)
59
- * - Regional indicator sequences
60
- * - Any other complex emoji grapheme clusters
61
- */
62
- export function extractEmojis(text) {
63
- const segments = graphemeSegmenter.segment(text);
64
- const emojis = [];
65
- for (const { segment } of segments) {
66
- // Check if this grapheme cluster contains emoji
67
- // First check for Emoji_Presentation (always displayed as emoji)
68
- if (emojiPresentationRegexCompiled.test(segment)) {
69
- emojis.push(segment);
70
- continue;
71
- }
72
- // Check for variation selector (U+FE0F) which indicates emoji presentation
73
- // But only if it follows an emoji-capable character to avoid false positives
74
- if (segment.includes("️")) {
75
- const beforeVS = segment.replace(/\uFE0F/g, "");
76
- if (emojiPropertyRegexCompiled.test(beforeVS)) {
77
- emojis.push(segment);
78
- }
79
- }
80
- }
81
- return emojis;
82
- }
83
- /**
84
- * Check if a version string is a valid emoji version
85
- */
86
- export function isValidEmojiVersion(version) {
87
- return EMOJI_VERSIONS.includes(version);
88
- }
89
- /**
90
- * Get the Unicode version for a given emoji character
91
- * Returns undefined if the emoji is not found in our data
92
- */
93
- export function getEmojiVersion(emoji) {
94
- // Get the first codepoint from the emoji
95
- const codepoint = emoji.codePointAt(0);
96
- if (codepoint === undefined) {
97
- return undefined;
98
- }
99
- // Check which range this codepoint falls into
100
- for (const range of EMOJI_RANGES) {
101
- if (codepoint >= range.start && codepoint <= range.end) {
102
- return range.version;
103
- }
104
- }
105
- return undefined;
106
- }
107
- /**
108
- * Filter function for emoji versions
109
- * Returns a filter function that checks if an emoji is from version or below
110
- */
111
- export function filterEmojis(version) {
112
- const maxVersion = parseVersion(version);
113
- return (emoji) => {
114
- const emojiVersion = getEmojiVersion(emoji);
115
- if (!emojiVersion) {
116
- // If we don't have version data, assume it's an older emoji
117
- // This is a conservative approach for unknown emoji
118
- return true;
119
- }
120
- return parseVersion(emojiVersion) <= maxVersion;
121
- };
122
- }
123
- /**
124
- * Get all emoji that match the filter
125
- * Generates emoji from codepoint ranges and filters them
126
- */
127
- export function getAllEmojis(filter) {
128
- const emojis = [];
129
- for (const range of EMOJI_RANGES) {
130
- for (let codepoint = range.start; codepoint <= range.end; codepoint++) {
131
- const emoji = String.fromCodePoint(codepoint);
132
- if (filter(emoji)) {
133
- emojis.push(emoji);
134
- }
135
- }
136
- }
137
- return emojis;
138
- }
139
- /**
140
- * Parse version string to comparable number
141
- * e.g., "12.0" -> 12.0, "13.1" -> 13.1
142
- */
143
- function parseVersion(version) {
144
- return parseFloat(version);
145
- }
package/messages.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export type CoreMessageIds = "parseError";
2
- export declare const CORE_MESSAGES: Record<CoreMessageIds, string>;
package/messages.js DELETED
@@ -1 +0,0 @@
1
- export const CORE_MESSAGES = { parseError: `Failed to parse message string {{error}}` };
@@ -1,14 +0,0 @@
1
- import type { Rule } from "eslint";
2
- export declare const name = "blocklist-elements";
3
- export declare enum Element {
4
- literal = "literal",
5
- argument = "argument",
6
- number = "number",
7
- date = "date",
8
- time = "time",
9
- select = "select",
10
- selectordinal = "selectordinal",
11
- plural = "plural",
12
- tag = "tag"
13
- }
14
- export declare const rule: Rule.RuleModule;
@@ -1,129 +0,0 @@
1
- import { isArgumentElement, isDateElement, isLiteralElement, isNumberElement, isPluralElement, isSelectElement, isTagElement, isTimeElement, parse } from "@formatjs/icu-messageformat-parser";
2
- import { extractMessages, getSettings } from "../util.js";
3
- import { CORE_MESSAGES } from "../messages.js";
4
- export const name = "blocklist-elements";
5
- function getMessage(type) {
6
- return {
7
- messageId: "blocklist",
8
- data: { type }
9
- };
10
- }
11
- export let Element = /* @__PURE__ */ function(Element) {
12
- Element["literal"] = "literal";
13
- Element["argument"] = "argument";
14
- Element["number"] = "number";
15
- Element["date"] = "date";
16
- Element["time"] = "time";
17
- Element["select"] = "select";
18
- Element["selectordinal"] = "selectordinal";
19
- Element["plural"] = "plural";
20
- Element["tag"] = "tag";
21
- return Element;
22
- }({});
23
- function verifyAst(blocklist, ast) {
24
- const errors = [];
25
- for (const el of ast) {
26
- if (isLiteralElement(el) && blocklist.includes(Element.literal)) {
27
- errors.push(getMessage(Element.literal));
28
- }
29
- if (isArgumentElement(el) && blocklist.includes(Element.argument)) {
30
- errors.push(getMessage(Element.argument));
31
- }
32
- if (isNumberElement(el) && blocklist.includes(Element.number)) {
33
- errors.push(getMessage(Element.number));
34
- }
35
- if (isDateElement(el) && blocklist.includes(Element.date)) {
36
- errors.push(getMessage(Element.date));
37
- }
38
- if (isTimeElement(el) && blocklist.includes(Element.time)) {
39
- errors.push(getMessage(Element.time));
40
- }
41
- if (isSelectElement(el) && blocklist.includes(Element.select)) {
42
- errors.push(getMessage(Element.select));
43
- }
44
- if (isTagElement(el) && blocklist.includes(Element.tag)) {
45
- errors.push(getMessage(Element.tag));
46
- }
47
- if (isPluralElement(el)) {
48
- if (blocklist.includes(Element.plural)) {
49
- errors.push(getMessage(Element.argument));
50
- }
51
- if (el.pluralType === "ordinal" && blocklist.includes(Element.selectordinal)) {
52
- errors.push(getMessage(Element.selectordinal));
53
- }
54
- }
55
- if (isSelectElement(el) || isPluralElement(el)) {
56
- const { options } = el;
57
- for (const selector of Object.keys(options)) {
58
- verifyAst(blocklist, options[selector].value);
59
- }
60
- }
61
- }
62
- return errors;
63
- }
64
- function checkNode(context, node) {
65
- const settings = getSettings(context);
66
- const msgs = extractMessages(node, settings);
67
- if (!msgs.length) {
68
- return;
69
- }
70
- const blocklist = context.options[0];
71
- if (!Array.isArray(blocklist) || !blocklist.length) {
72
- return;
73
- }
74
- for (const [{ message: { defaultMessage }, messageNode }] of msgs) {
75
- if (!defaultMessage || !messageNode) {
76
- continue;
77
- }
78
- let ast;
79
- try {
80
- ast = parse(defaultMessage, { ignoreTag: settings.ignoreTag });
81
- } catch (e) {
82
- context.report({
83
- node: messageNode,
84
- messageId: "parseError",
85
- data: { error: e.message }
86
- });
87
- continue;
88
- }
89
- const errors = verifyAst(blocklist, ast);
90
- for (const error of errors) {
91
- context.report({
92
- node: messageNode,
93
- ...error
94
- });
95
- }
96
- }
97
- }
98
- export const rule = {
99
- meta: {
100
- type: "problem",
101
- docs: {
102
- description: "Disallow specific elements in ICU message format",
103
- url: "https://formatjs.github.io/docs/tooling/linter#blocklist-elements"
104
- },
105
- fixable: "code",
106
- schema: [{
107
- type: "array",
108
- items: {
109
- type: "string",
110
- enum: Object.keys(Element)
111
- }
112
- }],
113
- messages: {
114
- ...CORE_MESSAGES,
115
- blocklist: `{{type}} element is blocklisted`
116
- }
117
- },
118
- create(context) {
119
- const callExpressionVisitor = (node) => checkNode(context, node);
120
- const parserServices = context.sourceCode.parserServices;
121
- if (parserServices?.defineTemplateBodyVisitor) {
122
- return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
123
- }
124
- return {
125
- JSXOpeningElement: (node) => checkNode(context, node),
126
- CallExpression: callExpressionVisitor
127
- };
128
- }
129
- };
@@ -1,7 +0,0 @@
1
- import type { Rule } from "eslint";
2
- export declare enum Option {
3
- literal = "literal",
4
- anything = "anything"
5
- }
6
- export declare const name = "enforce-default-message";
7
- export declare const rule: Rule.RuleModule;
@@ -1,57 +0,0 @@
1
- import { extractMessages, getSettings } from "../util.js";
2
- export let Option = /* @__PURE__ */ function(Option) {
3
- Option["literal"] = "literal";
4
- Option["anything"] = "anything";
5
- return Option;
6
- }({});
7
- export const name = "enforce-default-message";
8
- function checkNode(context, node) {
9
- const msgs = extractMessages(node, getSettings(context));
10
- const { options: [type] } = context;
11
- for (const [{ message: { defaultMessage }, messageNode, messageDescriptorNode }] of msgs) {
12
- if (!defaultMessage) {
13
- if (type === "literal" && messageNode) {
14
- context.report({
15
- node: messageNode,
16
- messageId: "defaultMessageLiteral"
17
- });
18
- } else if (!messageNode) {
19
- context.report({
20
- node: messageDescriptorNode,
21
- messageId: "defaultMessage"
22
- });
23
- }
24
- }
25
- }
26
- }
27
- export const rule = {
28
- meta: {
29
- type: "problem",
30
- docs: {
31
- description: "Enforce defaultMessage in message descriptor",
32
- url: "https://formatjs.github.io/docs/tooling/linter#enforce-default-message"
33
- },
34
- fixable: "code",
35
- schema: [{
36
- type: "string",
37
- enum: Object.keys(Option)
38
- }],
39
- messages: {
40
- defaultMessageLiteral: `"defaultMessage" must be:
41
- - a string literal or
42
- - template literal without variable`,
43
- defaultMessage: "`defaultMessage` has to be specified in message descriptor"
44
- }
45
- },
46
- create(context) {
47
- const callExpressionVisitor = (node) => checkNode(context, node);
48
- const parserServices = context.sourceCode.parserServices;
49
- if (parserServices?.defineTemplateBodyVisitor) {
50
- return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
51
- }
52
- return {
53
- JSXOpeningElement: (node) => checkNode(context, node),
54
- CallExpression: callExpressionVisitor
55
- };
56
- }
57
- };
@@ -1,11 +0,0 @@
1
- import type { Rule } from "eslint";
2
- export declare enum Option {
3
- literal = "literal",
4
- anything = "anything"
5
- }
6
- export type ObjectOption = {
7
- mode?: Option;
8
- minLength?: number;
9
- };
10
- export declare const name = "enforce-description";
11
- export declare const rule: Rule.RuleModule;
@@ -1,97 +0,0 @@
1
- import { extractMessages, getSettings } from "../util.js";
2
- export let Option = /* @__PURE__ */ function(Option) {
3
- Option["literal"] = "literal";
4
- Option["anything"] = "anything";
5
- return Option;
6
- }({});
7
- function normalizeOptions(raw) {
8
- if (typeof raw === "string") {
9
- return {
10
- mode: raw,
11
- minLength: undefined
12
- };
13
- }
14
- if (typeof raw === "object" && raw !== null) {
15
- return {
16
- mode: raw.mode,
17
- minLength: raw.minLength
18
- };
19
- }
20
- return {
21
- mode: undefined,
22
- minLength: undefined
23
- };
24
- }
25
- function checkNode(context, node) {
26
- const msgs = extractMessages(node, getSettings(context));
27
- const { mode: type, minLength } = normalizeOptions(context.options[0]);
28
- for (const [{ message: { description }, descriptionNode, messageDescriptorNode }] of msgs) {
29
- if (!description) {
30
- if (type === "literal" && descriptionNode) {
31
- context.report({
32
- node: descriptionNode,
33
- messageId: "enforceDescriptionLiteral"
34
- });
35
- } else if (!descriptionNode) {
36
- context.report({
37
- node: messageDescriptorNode,
38
- messageId: "enforceDescription"
39
- });
40
- }
41
- } else if (typeof minLength === "number" && typeof description === "string" && description.length < minLength) {
42
- context.report({
43
- node: descriptionNode ?? messageDescriptorNode,
44
- messageId: "enforceDescriptionMinLength",
45
- data: {
46
- minLength: String(minLength),
47
- length: String(description.length)
48
- }
49
- });
50
- }
51
- }
52
- }
53
- export const name = "enforce-description";
54
- export const rule = {
55
- meta: {
56
- type: "problem",
57
- docs: {
58
- description: "Enforce description in message descriptor",
59
- url: "https://formatjs.github.io/docs/tooling/linter#enforce-description"
60
- },
61
- fixable: "code",
62
- schema: [{ oneOf: [{
63
- type: "string",
64
- enum: Object.keys(Option)
65
- }, {
66
- type: "object",
67
- properties: {
68
- mode: {
69
- type: "string",
70
- enum: Object.keys(Option)
71
- },
72
- minLength: {
73
- type: "integer",
74
- minimum: 1
75
- }
76
- },
77
- additionalProperties: false,
78
- minProperties: 1
79
- }] }],
80
- messages: {
81
- enforceDescription: "`description` has to be specified in message descriptor",
82
- enforceDescriptionLiteral: "`description` has to be a string literal (not function call or variable)",
83
- enforceDescriptionMinLength: "`description` must be at least {{minLength}} characters long (currently {{length}})"
84
- }
85
- },
86
- create(context) {
87
- const callExpressionVisitor = (node) => checkNode(context, node);
88
- const parserServices = context.sourceCode.parserServices;
89
- if (parserServices?.defineTemplateBodyVisitor) {
90
- return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
91
- }
92
- return {
93
- JSXOpeningElement: (node) => checkNode(context, node),
94
- CallExpression: callExpressionVisitor
95
- };
96
- }
97
- };
@@ -1,8 +0,0 @@
1
- import type { Rule } from "eslint";
2
- export type Option = {
3
- idInterpolationPattern: string;
4
- idWhitelist?: string[];
5
- quoteStyle?: "single" | "double";
6
- };
7
- export declare const name = "enforce-id";
8
- export declare const rule: Rule.RuleModule;
@@ -1,135 +0,0 @@
1
- import { interpolateName } from "@formatjs/ts-transformer";
2
- import { extractMessages, getSettings } from "../util.js";
3
- function checkNode(context, node, { idInterpolationPattern, idWhitelistRegexps, quoteStyle }) {
4
- const msgs = extractMessages(node, getSettings(context));
5
- for (const [{ message: { defaultMessage, description, id }, idPropNode, descriptionNode, messagePropNode, messageDescriptorNode }] of msgs) {
6
- if (!idInterpolationPattern && !idPropNode) {
7
- context.report({
8
- node: messageDescriptorNode ?? node,
9
- messageId: "enforceId"
10
- });
11
- return;
12
- }
13
- if (idInterpolationPattern) {
14
- if (!defaultMessage) {
15
- context.report({
16
- node: messageDescriptorNode ?? node,
17
- messageId: "enforceIdDefaultMessage"
18
- });
19
- } else if (!description && descriptionNode) {
20
- context.report({
21
- node: messageDescriptorNode ?? node,
22
- messageId: "enforceIdDescription"
23
- });
24
- } else {
25
- if (idWhitelistRegexps && id && idWhitelistRegexps.some((r) => r.test(id))) {
26
- // messageId is allowlisted so skip interpolation id check
27
- continue;
28
- }
29
- const correctId = interpolateName({ resourcePath: context.filename }, idInterpolationPattern, { content: description ? `${defaultMessage}#${description}` : defaultMessage });
30
- if (id !== correctId) {
31
- let messageId = "enforceIdMatching";
32
- let messageData = {
33
- idInterpolationPattern,
34
- expected: correctId,
35
- actual: id
36
- };
37
- if (idWhitelistRegexps) {
38
- messageId = "enforceIdMatchingAllowlisted";
39
- messageData = {
40
- ...messageData,
41
- idWhitelist: idWhitelistRegexps.map((r) => `"${r.toString()}"`).join(", ")
42
- };
43
- }
44
- const quote = quoteStyle === "double" ? "\"" : "'";
45
- context.report({
46
- node: idPropNode ?? messageDescriptorNode ?? node,
47
- messageId,
48
- data: messageData,
49
- fix(fixer) {
50
- if (idPropNode) {
51
- if (idPropNode.type === "JSXAttribute") {
52
- // Always use double quotes for JSX attributes
53
- return fixer.replaceText(idPropNode, `id="${correctId}"`);
54
- }
55
- return fixer.replaceText(idPropNode, `id: ${quote}${correctId}${quote}`);
56
- }
57
- if (messagePropNode) {
58
- // Insert after default message node
59
- if (messagePropNode.type === "JSXAttribute") {
60
- // Always use double quotes for JSX attributes
61
- return fixer.insertTextAfter(messagePropNode, ` id="${correctId}"`);
62
- }
63
- return fixer.insertTextAfter(messagePropNode, `, id: ${quote}${correctId}${quote}`);
64
- }
65
- return null;
66
- }
67
- });
68
- }
69
- }
70
- }
71
- }
72
- }
73
- export const name = "enforce-id";
74
- export const rule = {
75
- meta: {
76
- type: "problem",
77
- docs: {
78
- description: "Enforce (generated) ID in message descriptor",
79
- url: "https://formatjs.github.io/docs/tooling/linter#enforce-id"
80
- },
81
- fixable: "code",
82
- schema: [{
83
- type: "object",
84
- properties: {
85
- idInterpolationPattern: {
86
- type: "string",
87
- description: "Pattern to verify ID against. Recommended value: [sha512:contenthash:base64:6]"
88
- },
89
- idWhitelist: {
90
- type: "array",
91
- description: "An array of strings with regular expressions. This array allows allowlist custom ids for messages. For example '`\\\\.`' allows any id which has dot; `'^payment_.*'` - allows any custom id which has prefix `payment_`. Be aware that any backslash \\ provided via string must be escaped with an additional backslash.",
92
- items: { type: "string" }
93
- },
94
- quoteStyle: {
95
- type: "string",
96
- enum: ["single", "double"],
97
- description: "Quote style for generated IDs. Defaults to single quotes."
98
- }
99
- },
100
- required: ["idInterpolationPattern"],
101
- additionalProperties: false
102
- }],
103
- messages: {
104
- enforceId: `id must be specified`,
105
- enforceIdDefaultMessage: `defaultMessage must be a string literal to calculate generated IDs`,
106
- enforceIdDescription: `description must be a string literal to calculate generated IDs`,
107
- enforceIdMatching: `"id" does not match with hash pattern {{idInterpolationPattern}}.
108
- Expected: {{expected}}
109
- Actual: {{actual}}`,
110
- enforceIdMatchingAllowlisted: `"id" does not match with hash pattern {{idInterpolationPattern}} or allowlisted patterns {{idWhitelist}}.
111
- Expected: {{expected}}
112
- Actual: {{actual}}`
113
- }
114
- },
115
- create(context) {
116
- const tmp = context.options[0];
117
- let opts = {
118
- idInterpolationPattern: tmp?.idInterpolationPattern,
119
- quoteStyle: tmp?.quoteStyle || "single"
120
- };
121
- if (Array.isArray(tmp?.idWhitelist)) {
122
- const { idWhitelist } = tmp;
123
- opts.idWhitelistRegexps = idWhitelist.map((str) => new RegExp(str, "i"));
124
- }
125
- const callExpressionVisitor = (node) => checkNode(context, node, opts);
126
- const parserServices = context.sourceCode.parserServices;
127
- if (parserServices?.defineTemplateBodyVisitor) {
128
- return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor }, { CallExpression: callExpressionVisitor });
129
- }
130
- return {
131
- JSXOpeningElement: (node) => checkNode(context, node, opts),
132
- CallExpression: callExpressionVisitor
133
- };
134
- }
135
- };
@@ -1,3 +0,0 @@
1
- import type { Rule } from "eslint";
2
- export declare const name = "enforce-placeholders";
3
- export declare const rule: Rule.RuleModule;