eslint-plugin-formatjs 8.0.5 → 8.1.0
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.js +87 -7
- package/index.js.map +1 -1
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -4952,9 +4952,60 @@ const rule$4 = {
|
|
|
4952
4952
|
//#endregion
|
|
4953
4953
|
//#region packages/eslint-plugin-formatjs/rules/prefer-formatted-message.ts
|
|
4954
4954
|
const name$5 = "prefer-formatted-message";
|
|
4955
|
+
function binding(context, node, name) {
|
|
4956
|
+
let scope = context.sourceCode.getScope(node);
|
|
4957
|
+
while (scope) {
|
|
4958
|
+
const variable = scope.set.get(name);
|
|
4959
|
+
if (variable) return variable;
|
|
4960
|
+
scope = scope.upper;
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4963
|
+
function importedName(specifier) {
|
|
4964
|
+
return specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
|
|
4965
|
+
}
|
|
4966
|
+
function isHookFormatter(context, callee) {
|
|
4967
|
+
const identifier = callee.type === "Identifier" ? callee : callee.type === "MemberExpression" && !callee.computed && !callee.optional && callee.property.type === "Identifier" && [
|
|
4968
|
+
"formatMessage",
|
|
4969
|
+
"$formatMessage",
|
|
4970
|
+
"$t"
|
|
4971
|
+
].includes(callee.property.name) && callee.object.type === "Identifier" ? callee.object : void 0;
|
|
4972
|
+
if (!identifier) return false;
|
|
4973
|
+
const variable = binding(context, callee, identifier.name);
|
|
4974
|
+
const definition = variable?.defs[0];
|
|
4975
|
+
if (definition?.type !== "Variable" || definition.parent?.kind !== "const" || variable?.references.some((reference) => reference.isWrite() && !reference.init)) return false;
|
|
4976
|
+
const { id, init } = definition.node;
|
|
4977
|
+
if (init?.type !== "CallExpression" || init.optional || init.arguments.length || init.callee.type !== "Identifier") return false;
|
|
4978
|
+
const hook = binding(context, init, init.callee.name)?.defs[0];
|
|
4979
|
+
if (hook?.type !== "ImportBinding" || hook.node.type !== "ImportSpecifier" || importedName(hook.node) !== "useIntl" || hook.node.importKind === "type" || hook.parent.importKind === "type" || hook.parent.source.value !== "react-intl") return false;
|
|
4980
|
+
if (callee.type === "MemberExpression") return id.type === "Identifier";
|
|
4981
|
+
return id.type === "ObjectPattern" && id.properties.some((property) => property.type === "Property" && !property.computed && property.key.type === "Identifier" && [
|
|
4982
|
+
"formatMessage",
|
|
4983
|
+
"$formatMessage",
|
|
4984
|
+
"$t"
|
|
4985
|
+
].includes(property.key.name) && property.value.type === "Identifier" && property.value.name === identifier.name);
|
|
4986
|
+
}
|
|
4987
|
+
function component(context, node) {
|
|
4988
|
+
const source = context.sourceCode;
|
|
4989
|
+
for (const declaration of source.ast.body) {
|
|
4990
|
+
if (declaration.type !== "ImportDeclaration" || declaration.source.value !== "react-intl" || declaration.importKind === "type") continue;
|
|
4991
|
+
for (const specifier of declaration.specifiers) if (specifier.type === "ImportSpecifier" && importedName(specifier) === "FormattedMessage" && /^[A-Z]/.test(specifier.local.name) && specifier.importKind !== "type" && binding(context, node, specifier.local.name)?.defs.some((definition) => definition.node === specifier)) return { name: specifier.local.name };
|
|
4992
|
+
}
|
|
4993
|
+
const occupied = new Set(source.scopeManager?.scopes.flatMap((scope) => [...scope.variables.map((variable) => variable.name), ...scope.references.map((reference) => reference.identifier.name)]));
|
|
4994
|
+
let local = "FormattedMessage";
|
|
4995
|
+
for (let suffix = 1; occupied.has(local); suffix++) local = "FormattedMessage" + suffix;
|
|
4996
|
+
const specifier = local === "FormattedMessage" ? local : "FormattedMessage as " + local;
|
|
4997
|
+
const declaration = source.ast.body.find((statement) => statement.type === "ImportDeclaration" && statement.source.value === "react-intl" && statement.importKind !== "type" && statement.specifiers.some((specifier) => specifier.type === "ImportSpecifier"));
|
|
4998
|
+
if (declaration?.type !== "ImportDeclaration") return;
|
|
4999
|
+
const anchor = declaration.specifiers.find((specifier) => specifier.type === "ImportSpecifier");
|
|
5000
|
+
return {
|
|
5001
|
+
name: local,
|
|
5002
|
+
insert: (fixer) => fixer.insertTextBefore(anchor, specifier + ", ")
|
|
5003
|
+
};
|
|
5004
|
+
}
|
|
4955
5005
|
const rule$3 = {
|
|
4956
5006
|
meta: {
|
|
4957
5007
|
type: "suggestion",
|
|
5008
|
+
fixable: "code",
|
|
4958
5009
|
docs: {
|
|
4959
5010
|
description: "Prefer `FormattedMessage` component over `intl.formatMessage` if applicable.",
|
|
4960
5011
|
url: "https://formatjs.github.io/docs/tooling/linter#prefer-formatted-message"
|
|
@@ -4964,15 +5015,44 @@ const rule$3 = {
|
|
|
4964
5015
|
},
|
|
4965
5016
|
create(context) {
|
|
4966
5017
|
const { additionalFunctionNames } = getSettings(context);
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
5018
|
+
const source = context.sourceCode;
|
|
5019
|
+
function checkChildren(node) {
|
|
5020
|
+
for (const child of node.children) {
|
|
5021
|
+
if (child.type !== "JSXExpressionContainer" || !isIntlFormatMessageCall(child.expression, additionalFunctionNames)) continue;
|
|
4970
5022
|
context.report({
|
|
4971
5023
|
node: child,
|
|
4972
|
-
messageId: "jsxChildren"
|
|
5024
|
+
messageId: "jsxChildren",
|
|
5025
|
+
fix(fixer) {
|
|
5026
|
+
const call = child.expression;
|
|
5027
|
+
if (call.type !== "CallExpression" || call.optional || call.arguments.length > 2 || !isHookFormatter(context, call.callee) || source.getCommentsInside(child).length || call.typeArguments || call.typeParameters) return null;
|
|
5028
|
+
const [descriptor, values] = call.arguments;
|
|
5029
|
+
if (descriptor?.type !== "ObjectExpression" || values?.type === "SpreadElement") return null;
|
|
5030
|
+
const attributes = [];
|
|
5031
|
+
const keys = /* @__PURE__ */ new Set();
|
|
5032
|
+
for (const property of descriptor.properties) {
|
|
5033
|
+
if (property.type !== "Property" || property.computed || property.method || property.kind !== "init") return null;
|
|
5034
|
+
const key = property.key.type === "Identifier" ? property.key.name : property.key.type === "Literal" ? property.key.value : void 0;
|
|
5035
|
+
if (typeof key !== "string" || ![
|
|
5036
|
+
"id",
|
|
5037
|
+
"defaultMessage",
|
|
5038
|
+
"description"
|
|
5039
|
+
].includes(key) || keys.has(key)) return null;
|
|
5040
|
+
keys.add(key);
|
|
5041
|
+
attributes.push(key + "={(" + source.getText(property.value) + ")}");
|
|
5042
|
+
}
|
|
5043
|
+
if (values) attributes.push("values={(" + source.getText(values) + ")}");
|
|
5044
|
+
const target = component(context, child);
|
|
5045
|
+
if (!target) return null;
|
|
5046
|
+
const replacement = "<" + target.name + (attributes.length ? " " + attributes.join(" ") : "") + " />";
|
|
5047
|
+
return [...target.insert ? [target.insert(fixer)] : [], fixer.replaceText(child, replacement)];
|
|
5048
|
+
}
|
|
4973
5049
|
});
|
|
4974
|
-
}
|
|
4975
|
-
}
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
return {
|
|
5053
|
+
JSXElement: checkChildren,
|
|
5054
|
+
JSXFragment: checkChildren
|
|
5055
|
+
};
|
|
4976
5056
|
}
|
|
4977
5057
|
};
|
|
4978
5058
|
//#endregion
|
|
@@ -5269,7 +5349,7 @@ var package_exports = /* @__PURE__ */ __exportAll({
|
|
|
5269
5349
|
version: () => version$1
|
|
5270
5350
|
});
|
|
5271
5351
|
var name$1 = "eslint-plugin-formatjs";
|
|
5272
|
-
var version$1 = "8.0
|
|
5352
|
+
var version$1 = "8.1.0";
|
|
5273
5353
|
var description = "ESLint plugin for formatjs";
|
|
5274
5354
|
var keywords = [
|
|
5275
5355
|
"eslint",
|