eslint-plugin-formatjs 6.7.0-rc.0 → 7.0.0-rc.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 -12
- package/index.js.map +1 -1
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -61,7 +61,7 @@ function messageTypes(ast, module, ignoreList = []) {
|
|
|
61
61
|
else if (types.has("date")) type = "number | Date";
|
|
62
62
|
else if (types.has("select")) type = "string";
|
|
63
63
|
else type = `import(${JSON.stringify(module)}).MessageTag`;
|
|
64
|
-
return
|
|
64
|
+
return `readonly ${JSON.stringify(name)}${ignored.has(name) ? "?" : ""}: ${type}`;
|
|
65
65
|
});
|
|
66
66
|
return fields.length ? `{ ${fields.join("; ")} }` : "{}";
|
|
67
67
|
}
|
|
@@ -455,7 +455,13 @@ function renderType(node) {
|
|
|
455
455
|
TSStringKeyword: "string"
|
|
456
456
|
};
|
|
457
457
|
if (keywords[node.type]) return keywords[node.type];
|
|
458
|
-
if (node.type === "TSTypeReference" && node.typeName?.type === "Identifier")
|
|
458
|
+
if (node.type === "TSTypeReference" && node.typeName?.type === "Identifier") {
|
|
459
|
+
const parameters = (node.typeArguments ?? node.typeParameters)?.params;
|
|
460
|
+
if (!parameters) return node.typeName.name;
|
|
461
|
+
const types = parameters.map(renderType);
|
|
462
|
+
if (types.every((type) => type !== void 0)) return node.typeName.name + "<" + types.join(", ") + ">";
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
459
465
|
if (node.type === "TSUnionType") {
|
|
460
466
|
const types = node.types?.map(renderType);
|
|
461
467
|
if (types?.every((type) => type !== void 0)) return types.join(" | ");
|
|
@@ -467,11 +473,11 @@ function renderType(node) {
|
|
|
467
473
|
if (node.type === "TSTypeLiteral" && node.members) {
|
|
468
474
|
const fields = [];
|
|
469
475
|
for (const member of node.members) {
|
|
470
|
-
if (member.type !== "TSPropertySignature" || member.
|
|
476
|
+
if (member.type !== "TSPropertySignature" || member.computed) return;
|
|
471
477
|
const key = member.key?.type === "Identifier" ? member.key.name : member.key?.value;
|
|
472
478
|
const type = renderType(member.typeAnnotation?.typeAnnotation);
|
|
473
479
|
if (key === void 0 || type === void 0) return;
|
|
474
|
-
fields.push(`${JSON.stringify(String(key))}${member.optional ? "?" : ""}: ${type}`);
|
|
480
|
+
fields.push(`${member.readonly ? "readonly " : ""}${JSON.stringify(String(key))}${member.optional ? "?" : ""}: ${type}`);
|
|
475
481
|
}
|
|
476
482
|
return fields.length ? `{ ${fields.join("; ")} }` : "{}";
|
|
477
483
|
}
|
|
@@ -482,7 +488,7 @@ function hoistTypes(context, node, contract) {
|
|
|
482
488
|
const occupied = new Set(source.scopeManager?.scopes.flatMap((scope) => scope.variables.map((variable) => variable.name)));
|
|
483
489
|
const resolved = /* @__PURE__ */ new Map();
|
|
484
490
|
return {
|
|
485
|
-
text: contract.replace(/import\(("(?:[^"\\]|\\.)*")\)\.(MessageTag|MessageValue)/g, (reference, quotedModule, imported) => {
|
|
491
|
+
text: contract.replace(/import\(("(?:[^"\\]|\\.)*")\)\.(MessageTag|MessageValue|TypedMessageDescriptor)/g, (reference, quotedModule, imported) => {
|
|
486
492
|
if (resolved.has(reference)) return resolved.get(reference);
|
|
487
493
|
const module = JSON.parse(quotedModule);
|
|
488
494
|
for (const declaration of source.ast.body) {
|
|
@@ -519,6 +525,54 @@ function hoistTypes(context, node, contract) {
|
|
|
519
525
|
}
|
|
520
526
|
};
|
|
521
527
|
}
|
|
528
|
+
function catalogAnnotation(context, node) {
|
|
529
|
+
const parent = node.parent;
|
|
530
|
+
if (parent?.type !== "VariableDeclarator" || parent.init !== node) return;
|
|
531
|
+
const annotation = parent.id.typeAnnotation?.typeAnnotation;
|
|
532
|
+
if (!annotation) return;
|
|
533
|
+
function imported(type, name) {
|
|
534
|
+
if (type.type !== "TSTypeReference" || type.typeName?.type !== "Identifier") return false;
|
|
535
|
+
let scope = context.sourceCode.getScope(node);
|
|
536
|
+
while (scope) {
|
|
537
|
+
const binding = scope.set.get(type.typeName.name);
|
|
538
|
+
if (binding) {
|
|
539
|
+
const definition = binding.defs[0];
|
|
540
|
+
return definition?.type === "ImportBinding" && descriptorModules.has(String(definition.parent.source.value)) && definition.node.type === "ImportSpecifier" && (definition.node.imported.type === "Identifier" ? definition.node.imported.name : definition.node.imported.value) === name;
|
|
541
|
+
}
|
|
542
|
+
scope = scope.upper;
|
|
543
|
+
}
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
function broad(type) {
|
|
547
|
+
if (type.type === "TSTypeLiteral") return !!type.members?.every((member) => member.type === "TSPropertySignature" && !!member.typeAnnotation && imported(member.typeAnnotation.typeAnnotation, "MessageDescriptor"));
|
|
548
|
+
let scope = context.sourceCode.getScope(node);
|
|
549
|
+
while (scope) {
|
|
550
|
+
if (scope.set.get(type.typeName?.name ?? "")?.defs.length) return false;
|
|
551
|
+
scope = scope.upper;
|
|
552
|
+
}
|
|
553
|
+
const parameters = (type.typeArguments ?? type.typeParameters)?.params;
|
|
554
|
+
if (type.typeName?.name === "Readonly" && parameters?.length === 1) return broad(parameters[0]);
|
|
555
|
+
return type.typeName?.name === "Record" && parameters?.length === 2 && imported(parameters[1], "MessageDescriptor");
|
|
556
|
+
}
|
|
557
|
+
function typedMap(type) {
|
|
558
|
+
return type.type === "TSTypeLiteral" && !!type.members?.every((member) => member.type === "TSPropertySignature" && !member.computed && !!member.typeAnnotation && imported(member.typeAnnotation.typeAnnotation, "TypedMessageDescriptor"));
|
|
559
|
+
}
|
|
560
|
+
let base = annotation;
|
|
561
|
+
let generated = typedMap(annotation) ? annotation : void 0;
|
|
562
|
+
if (annotation.type === "TSIntersectionType" && annotation.types?.length === 2) {
|
|
563
|
+
const [left, right] = annotation.types;
|
|
564
|
+
if (right.type === "TSTypeLiteral" && right.members?.every((member) => member.type === "TSPropertySignature" && !!member.typeAnnotation && imported(member.typeAnnotation.typeAnnotation, "TypedMessageDescriptor"))) {
|
|
565
|
+
base = left;
|
|
566
|
+
generated = right;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return {
|
|
570
|
+
annotation,
|
|
571
|
+
base,
|
|
572
|
+
generated,
|
|
573
|
+
supported: broad(base) || typedMap(base)
|
|
574
|
+
};
|
|
575
|
+
}
|
|
522
576
|
const descriptorModules = /* @__PURE__ */ new Set([
|
|
523
577
|
"@formatjs/intl",
|
|
524
578
|
"react-intl",
|
|
@@ -623,6 +677,7 @@ const rule$20 = {
|
|
|
623
677
|
contract: "Generate or refresh the ICU argument contract.",
|
|
624
678
|
invalid: "Cannot generate message types: {{error}}.",
|
|
625
679
|
manual: "Unsupported number of generic arguments; update the call manually.",
|
|
680
|
+
annotation: "Catalog annotation may erase ICU contracts; use per-message TypedMessageDescriptor types.",
|
|
626
681
|
dynamic: "Typed messages require static message strings and parser options for contract verification."
|
|
627
682
|
}
|
|
628
683
|
},
|
|
@@ -742,14 +797,32 @@ const rule$20 = {
|
|
|
742
797
|
});
|
|
743
798
|
return;
|
|
744
799
|
}
|
|
800
|
+
const annotation = imported.helper === "defineMessages" ? catalogAnnotation(context, node) : void 0;
|
|
801
|
+
if (annotation && !annotation.supported) {
|
|
802
|
+
context.report({
|
|
803
|
+
node: annotation.annotation,
|
|
804
|
+
messageId: "annotation"
|
|
805
|
+
});
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
745
808
|
let contract;
|
|
809
|
+
let catalogType;
|
|
746
810
|
try {
|
|
747
811
|
const types = messages.map(([message]) => messageTypes(parse(message.message.defaultMessage, { ignoreTag: getSettings(context).ignoreTag }), imported.module, context.options[0]?.ignoreList));
|
|
812
|
+
if (annotation) {
|
|
813
|
+
catalogType = "{ " + descriptor.properties.map((property, index) => {
|
|
814
|
+
if (property.type !== "Property") throw new Error("Unexpected spread");
|
|
815
|
+
const key = property.key.type === "Identifier" ? property.key.name : property.key.type === "Literal" ? String(property.key.value) : void 0;
|
|
816
|
+
if (key === void 0) throw new Error("Unsupported catalog key");
|
|
817
|
+
return "readonly " + JSON.stringify(key) + ": import(" + JSON.stringify(imported.module) + ").TypedMessageDescriptor<" + types[index] + ">";
|
|
818
|
+
}).join("; ") + " }";
|
|
819
|
+
if (!descriptor.properties.length) catalogType = "{}";
|
|
820
|
+
}
|
|
748
821
|
contract = imported.helper === "defineMessage" ? types[0] : descriptor.properties.length === 0 ? "{}" : `{ ${descriptor.properties.map((p, i) => {
|
|
749
822
|
if (p.type !== "Property") throw new Error("Unexpected spread");
|
|
750
823
|
const key = p.key.type === "Identifier" ? p.key.name : p.key.type === "Literal" ? String(p.key.value) : void 0;
|
|
751
824
|
if (key === void 0) throw new Error("Unsupported catalog key");
|
|
752
|
-
return
|
|
825
|
+
return `readonly ${JSON.stringify(key)}: ${types[i]}`;
|
|
753
826
|
}).join("; ")} }`;
|
|
754
827
|
} catch (error) {
|
|
755
828
|
context.report({
|
|
@@ -759,12 +832,13 @@ const rule$20 = {
|
|
|
759
832
|
});
|
|
760
833
|
return;
|
|
761
834
|
}
|
|
762
|
-
const imports = hoistTypes(context, node, contract);
|
|
763
|
-
contract = imports.text;
|
|
835
|
+
const imports = hoistTypes(context, node, contract + (catalogType ? "\n" + catalogType : ""));
|
|
836
|
+
[contract, catalogType] = imports.text.split("\n");
|
|
837
|
+
const annotationMatches = !annotation || annotation.annotation === annotation.generated && renderType(annotation.generated) === catalogType;
|
|
764
838
|
const expected = `<${contract}>`;
|
|
765
839
|
const parameters = generic?.params;
|
|
766
|
-
if (parameters
|
|
767
|
-
if (generic && parameters
|
|
840
|
+
if (parameters && parameters.length >= 1 && parameters.length <= 2 && renderType(parameters[0]) === contract && typed && !generated && annotationMatches) return;
|
|
841
|
+
if (generic && (!parameters || parameters.length < 1 || parameters.length > 2)) {
|
|
768
842
|
context.report({
|
|
769
843
|
node: generic,
|
|
770
844
|
messageId: "manual"
|
|
@@ -775,7 +849,8 @@ const rule$20 = {
|
|
|
775
849
|
node,
|
|
776
850
|
messageId: "contract",
|
|
777
851
|
fix(fixer) {
|
|
778
|
-
const edits = [...imports.fix(fixer), generic ? fixer.
|
|
852
|
+
const edits = [...imports.fix(fixer), generic && parameters?.[0] ? fixer.replaceTextRange([generic.range[0] + 1, parameters[0].range[1]], contract) : fixer.insertTextAfter(node.optional ? source.getTokenAfter(node.callee) : node.callee, expected)];
|
|
853
|
+
if (annotation && !annotationMatches) edits.push(fixer.replaceText(annotation.annotation, catalogType));
|
|
779
854
|
if (!typed) {
|
|
780
855
|
const close = source.getLastToken(node);
|
|
781
856
|
const previous = source.getTokenBefore(close);
|
|
@@ -5049,7 +5124,7 @@ var package_exports = /* @__PURE__ */ __exportAll({
|
|
|
5049
5124
|
version: () => version$1
|
|
5050
5125
|
});
|
|
5051
5126
|
var name$1 = "eslint-plugin-formatjs";
|
|
5052
|
-
var version$1 = "
|
|
5127
|
+
var version$1 = "7.0.0-rc.0";
|
|
5053
5128
|
var description = "ESLint plugin for formatjs";
|
|
5054
5129
|
var keywords = [
|
|
5055
5130
|
"eslint",
|