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.
- package/index.d.ts +15 -12
- package/index.js +4603 -54
- package/index.js.map +1 -0
- package/package.json +3 -3
- package/util.d.ts +654 -29
- package/util.js +62 -134
- package/util.js.map +1 -0
- package/emoji-data.generated.d.ts +0 -27
- package/emoji-data.generated.js +0 -2564
- package/emoji-utils.d.ts +0 -43
- package/emoji-utils.js +0 -145
- package/messages.d.ts +0 -2
- package/messages.js +0 -1
- package/rules/blocklist-elements.d.ts +0 -14
- package/rules/blocklist-elements.js +0 -129
- package/rules/enforce-default-message.d.ts +0 -7
- package/rules/enforce-default-message.js +0 -57
- package/rules/enforce-description.d.ts +0 -11
- package/rules/enforce-description.js +0 -97
- package/rules/enforce-id.d.ts +0 -8
- package/rules/enforce-id.js +0 -135
- package/rules/enforce-placeholders.d.ts +0 -3
- package/rules/enforce-placeholders.js +0 -128
- package/rules/enforce-plural-rules.d.ts +0 -14
- package/rules/enforce-plural-rules.js +0 -108
- package/rules/no-camel-case.d.ts +0 -3
- package/rules/no-camel-case.js +0 -85
- package/rules/no-complex-selectors.d.ts +0 -3
- package/rules/no-complex-selectors.js +0 -119
- package/rules/no-emoji.d.ts +0 -8
- package/rules/no-emoji.js +0 -88
- package/rules/no-id.d.ts +0 -3
- package/rules/no-id.js +0 -48
- package/rules/no-invalid-icu.d.ts +0 -3
- package/rules/no-invalid-icu.js +0 -56
- package/rules/no-literal-string-in-jsx.d.ts +0 -3
- package/rules/no-literal-string-in-jsx.js +0 -161
- package/rules/no-literal-string-in-object.d.ts +0 -3
- package/rules/no-literal-string-in-object.js +0 -59
- package/rules/no-missing-icu-plural-one-placeholders.d.ts +0 -5
- package/rules/no-missing-icu-plural-one-placeholders.js +0 -94
- package/rules/no-multiple-plurals.d.ts +0 -3
- package/rules/no-multiple-plurals.js +0 -76
- package/rules/no-multiple-whitespaces.d.ts +0 -3
- package/rules/no-multiple-whitespaces.js +0 -126
- package/rules/no-offset.d.ts +0 -3
- package/rules/no-offset.js +0 -75
- package/rules/no-useless-message.d.ts +0 -3
- package/rules/no-useless-message.js +0 -69
- package/rules/prefer-formatted-message.d.ts +0 -3
- package/rules/prefer-formatted-message.js +0 -26
- package/rules/prefer-full-sentence.d.ts +0 -3
- package/rules/prefer-full-sentence.js +0 -111
- package/rules/prefer-pound-in-plural.d.ts +0 -3
- 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
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,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
|
-
};
|
package/rules/enforce-id.d.ts
DELETED
package/rules/enforce-id.js
DELETED
|
@@ -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
|
-
};
|