eslint-plugin-formatjs 8.0.0 → 8.0.2

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 (3) hide show
  1. package/index.js +195 -52
  2. package/index.js.map +1 -1
  3. package/package.json +1 -1
package/index.js CHANGED
@@ -385,6 +385,44 @@ const modules = /* @__PURE__ */ new Set([
385
385
  function staticObject(node) {
386
386
  return node?.type === "ObjectExpression" && node.properties.every((property) => property.type === "Property" && !property.computed && property.kind === "init" && !property.method);
387
387
  }
388
+ function resolveDescriptor(context, node, seen = /* @__PURE__ */ new Set()) {
389
+ if (!node || seen.has(node)) return;
390
+ if (node.type !== "Identifier") return node;
391
+ seen.add(node);
392
+ let scope = context.sourceCode.getScope(node);
393
+ while (scope) {
394
+ const variable = scope.set.get(node.name);
395
+ if (variable) {
396
+ const definition = variable.defs[0];
397
+ if (definition?.type !== "Variable" || definition.parent?.kind !== "const" || !definition.node.init) return;
398
+ if (variable.references.some((reference) => {
399
+ if (reference.init) return false;
400
+ const parent = reference.identifier.parent;
401
+ if (parent?.type === "Property" && parent.value === reference.identifier) {
402
+ const object = parent.parent;
403
+ const call = object?.parent;
404
+ if (call?.type === "CallExpression" && call.arguments[0] === object && importedHelper(context, call)?.helper === "defineMessages") return false;
405
+ }
406
+ return parent?.type !== "CallExpression" || parent.arguments[0] !== reference.identifier || !importedHelper(context, parent) && !isIntlFormatMessageCall({
407
+ ...parent,
408
+ arguments: [{
409
+ type: "ObjectExpression",
410
+ properties: []
411
+ }]
412
+ }, getSettings(context).additionalFunctionNames);
413
+ })) return;
414
+ return resolveDescriptor(context, definition.node.init, seen);
415
+ }
416
+ scope = scope.upper;
417
+ }
418
+ }
419
+ function typeQuery(node) {
420
+ if (node?.type === "Identifier") return node.name;
421
+ if (node?.type === "MemberExpression" && !node.computed && node.property.type === "Identifier") {
422
+ const object = typeQuery(node.object);
423
+ if (object) return object + "." + node.property.name;
424
+ }
425
+ }
388
426
  function staticMessage(node) {
389
427
  if (!node) return false;
390
428
  if (node.type === "Literal") return typeof node.value === "string";
@@ -464,6 +502,16 @@ function renderType(node) {
464
502
  if (types.every((type) => type !== void 0)) return node.typeName.name + "<" + types.join(", ") + ">";
465
503
  return;
466
504
  }
505
+ if (node.type === "Identifier") return node.name;
506
+ if (node.type === "TSQualifiedName") {
507
+ const left = renderType(node.left);
508
+ const right = renderType(node.right);
509
+ if (left && right) return left + "." + right;
510
+ }
511
+ if (node.type === "TSTypeQuery") {
512
+ const name = renderType(node.exprName);
513
+ if (name) return "typeof " + name;
514
+ }
467
515
  if (node.type === "TSUnionType") {
468
516
  const types = node.types?.map(renderType);
469
517
  if (types?.every((type) => type !== void 0)) return types.join(" | ");
@@ -475,11 +523,11 @@ function renderType(node) {
475
523
  if (node.type === "TSTypeLiteral" && node.members) {
476
524
  const fields = [];
477
525
  for (const member of node.members) {
478
- if (member.type !== "TSPropertySignature" || member.computed) return;
526
+ if (member.type !== "TSPropertySignature") return;
479
527
  const key = member.key?.type === "Identifier" ? member.key.name : member.key?.value;
480
528
  const type = renderType(member.typeAnnotation?.typeAnnotation);
481
- if (key === void 0 || type === void 0) return;
482
- fields.push(`${member.readonly ? "readonly " : ""}${JSON.stringify(String(key))}${member.optional ? "?" : ""}: ${type}`);
529
+ if (!member.computed && key === void 0 || type === void 0) return;
530
+ fields.push(`${member.readonly ? "readonly " : ""}${member.computed ? "[" + (typeQuery(member.key) ?? renderType(member.key)) + "]" : JSON.stringify(String(key))}${member.optional ? "?" : ""}: ${type}`);
483
531
  }
484
532
  return fields.length ? `{ ${fields.join("; ")} }` : "{}";
485
533
  }
@@ -490,7 +538,7 @@ function hoistTypes(context, node, contract) {
490
538
  const occupied = new Set(source.scopeManager?.scopes.flatMap((scope) => scope.variables.map((variable) => variable.name)));
491
539
  const resolved = /* @__PURE__ */ new Map();
492
540
  return {
493
- text: contract.replace(/import\(("(?:[^"\\]|\\.)*")\)\.(MessageTag|MessageValue|TypedMessageDescriptor)/g, (reference, quotedModule, imported) => {
541
+ text: contract.replace(/import\(("(?:[^"\\]|\\.)*")\)\.(MessageTag|MessageValuesOf|MessageValue|TypedMessageDescriptor)/g, (reference, quotedModule, imported) => {
494
542
  if (resolved.has(reference)) return resolved.get(reference);
495
543
  const module = JSON.parse(quotedModule);
496
544
  for (const declaration of source.ast.body) {
@@ -527,14 +575,78 @@ function hoistTypes(context, node, contract) {
527
575
  }
528
576
  };
529
577
  }
530
- function catalogAnnotation(context, node) {
578
+ function obsoleteTypes(context, replaced, replacement, fixer) {
579
+ const source = context.sourceCode;
580
+ const removed = [...replaced];
581
+ const declarations = /* @__PURE__ */ new Set();
582
+ const retainedNames = new Set(replacement.match(/[$\p{ID_Start}][$\p{ID_Continue}]*/gu));
583
+ const contains = (node) => removed.some((range) => range.range && node.range && range.range[0] <= node.range[0] && range.range[1] >= node.range[1]);
584
+ let changed = true;
585
+ while (changed) {
586
+ changed = false;
587
+ for (const scope of source.scopeManager?.scopes ?? []) for (const variable of scope.variables) {
588
+ if (variable.defs.length !== 1 || !variable.references.length || !variable.references.every((reference) => contains(reference.identifier))) continue;
589
+ if (retainedNames.has(variable.name)) continue;
590
+ const definition = variable.defs[0];
591
+ const declaration = definition.node;
592
+ if (declarations.has(declaration)) continue;
593
+ if (declaration.type === "ImportSpecifier") {
594
+ const imported = declaration.imported.type === "Identifier" ? declaration.imported.name : declaration.imported.value;
595
+ if (declaration.importKind !== "type" && definition.parent?.importKind !== "type" && !(definition.type === "ImportBinding" && descriptorModules.has(String(definition.parent.source.value)) && ["MessageDescriptor", "TypedMessageDescriptor"].includes(String(imported)))) continue;
596
+ } else if (declaration.type !== "TSTypeAliasDeclaration" || declaration.parent?.type === "ExportNamedDeclaration") continue;
597
+ declarations.add(declaration);
598
+ removed.push(declaration);
599
+ changed = true;
600
+ }
601
+ }
602
+ const edits = [];
603
+ for (const scope of source.scopeManager?.scopes ?? []) for (const variable of scope.variables) {
604
+ if (variable.defs.length !== 1 || retainedNames.has(variable.name) || declarations.has(variable.defs[0].node)) continue;
605
+ if (variable.references.some((reference) => contains(reference.identifier))) {
606
+ const identifier = variable.identifiers[0];
607
+ if (identifier && !contains(identifier)) edits.push(fixer.replaceText(identifier, source.getText(identifier)));
608
+ }
609
+ }
610
+ const imports = /* @__PURE__ */ new Map();
611
+ for (const declaration of declarations) {
612
+ if (declaration.type !== "ImportSpecifier") {
613
+ edits.push(fixer.remove(declaration));
614
+ continue;
615
+ }
616
+ const parent = declaration.parent;
617
+ const specifiers = imports.get(parent) ?? /* @__PURE__ */ new Set();
618
+ specifiers.add(declaration);
619
+ imports.set(parent, specifiers);
620
+ }
621
+ for (const [declaration, removedSpecifiers] of imports) {
622
+ if (declaration.type !== "ImportDeclaration") continue;
623
+ if (declaration.specifiers.every((specifier) => removedSpecifiers.has(specifier))) {
624
+ edits.push(fixer.remove(declaration));
625
+ continue;
626
+ }
627
+ for (let i = 0; i < declaration.specifiers.length; i++) {
628
+ const first = declaration.specifiers[i];
629
+ if (!removedSpecifiers.has(first)) continue;
630
+ let last = first;
631
+ while (i + 1 < declaration.specifiers.length && removedSpecifiers.has(declaration.specifiers[i + 1])) last = declaration.specifiers[++i];
632
+ const next = source.getTokenAfter(last);
633
+ if (next.value === ",") edits.push(fixer.removeRange([first.range[0], next.range[1] + (source.text.slice(next.range[1]).match(/^\s*/)?.[0].length ?? 0)]));
634
+ else {
635
+ const previous = source.getTokenBefore(first);
636
+ edits.push(fixer.removeRange([previous.value === "," ? previous.range[0] : first.range[0], last.range[1]]));
637
+ }
638
+ }
639
+ }
640
+ return edits;
641
+ }
642
+ function messageAnnotation(context, node, catalog) {
531
643
  const parent = node.parent;
532
644
  if (parent?.type !== "VariableDeclarator" || parent.init !== node) return;
533
645
  const annotation = parent.id.typeAnnotation?.typeAnnotation;
534
646
  if (!annotation) return;
535
647
  function imported(type, name) {
536
648
  if (type.type !== "TSTypeReference" || type.typeName?.type !== "Identifier") return false;
537
- let scope = context.sourceCode.getScope(node);
649
+ let scope = context.sourceCode.getScope(type);
538
650
  while (scope) {
539
651
  const binding = scope.set.get(type.typeName.name);
540
652
  if (binding) {
@@ -545,23 +657,34 @@ function catalogAnnotation(context, node) {
545
657
  }
546
658
  return false;
547
659
  }
548
- function broad(type) {
549
- if (type.type === "TSTypeLiteral") return !!type.members?.every((member) => member.type === "TSPropertySignature" && !!member.typeAnnotation && imported(member.typeAnnotation.typeAnnotation, "MessageDescriptor"));
550
- let scope = context.sourceCode.getScope(node);
660
+ function descriptorType(type) {
661
+ return imported(type, "MessageDescriptor") || type.type === "TSUnionType" && !!type.types?.every((part) => part.type === "TSUndefinedKeyword" || imported(part, "MessageDescriptor"));
662
+ }
663
+ function broad(type, seen = /* @__PURE__ */ new Set()) {
664
+ if (seen.has(type)) return false;
665
+ seen.add(type);
666
+ if (!catalog && imported(type, "MessageDescriptor")) return true;
667
+ if (type.type === "TSTypeLiteral") return catalog && !!type.members?.every((member) => member.type === "TSPropertySignature" && !!member.typeAnnotation && descriptorType(member.typeAnnotation.typeAnnotation));
668
+ let scope = context.sourceCode.getScope(type);
551
669
  while (scope) {
552
- if (scope.set.get(type.typeName?.name ?? "")?.defs.length) return false;
670
+ const binding = scope.set.get(type.typeName?.name ?? "");
671
+ if (binding?.defs.length) {
672
+ const declaration = binding.defs[0].node;
673
+ return declaration.type === "TSTypeAliasDeclaration" && !declaration.typeParameters && !!declaration.typeAnnotation && broad(declaration.typeAnnotation, seen);
674
+ }
553
675
  scope = scope.upper;
554
676
  }
555
677
  const parameters = (type.typeArguments ?? type.typeParameters)?.params;
556
- if (type.typeName?.name === "Readonly" && parameters?.length === 1) return broad(parameters[0]);
557
- return type.typeName?.name === "Record" && parameters?.length === 2 && imported(parameters[1], "MessageDescriptor");
678
+ if (type.typeName?.name === "Readonly" && parameters?.length === 1) return broad(parameters[0], seen);
679
+ return catalog && type.typeName?.name === "Record" && parameters?.length === 2 && descriptorType(parameters[1]);
558
680
  }
559
681
  function typedMap(type) {
560
- return type.type === "TSTypeLiteral" && !!type.members?.every((member) => member.type === "TSPropertySignature" && !member.computed && !!member.typeAnnotation && imported(member.typeAnnotation.typeAnnotation, "TypedMessageDescriptor"));
682
+ if (!catalog) return imported(type, "TypedMessageDescriptor");
683
+ return type.type === "TSTypeLiteral" && !!type.members?.every((member) => member.type === "TSPropertySignature" && !!member.typeAnnotation && imported(member.typeAnnotation.typeAnnotation, "TypedMessageDescriptor"));
561
684
  }
562
685
  let base = annotation;
563
686
  let generated = typedMap(annotation) ? annotation : void 0;
564
- if (annotation.type === "TSIntersectionType" && annotation.types?.length === 2) {
687
+ if (catalog && annotation.type === "TSIntersectionType" && annotation.types?.length === 2) {
565
688
  const [left, right] = annotation.types;
566
689
  if (right.type === "TSTypeLiteral" && right.members?.every((member) => member.type === "TSPropertySignature" && !!member.typeAnnotation && imported(member.typeAnnotation.typeAnnotation, "TypedMessageDescriptor"))) {
567
690
  base = left;
@@ -598,7 +721,7 @@ function checkInlineMessage(context, node) {
598
721
  const generic = call.typeArguments ?? call.typeParameters;
599
722
  if (!generic && !context.options[0]?.generateTypes) return;
600
723
  const generated = !!generic && /^<\s*\/\* @formatjs-generated \*\//.test(source.getText(generic));
601
- const descriptor = node.arguments[0];
724
+ const descriptor = resolveDescriptor(context, node.arguments[0]);
602
725
  if (!staticObject(descriptor)) {
603
726
  if (generic) context.report({
604
727
  node,
@@ -616,7 +739,10 @@ function checkInlineMessage(context, node) {
616
739
  });
617
740
  return !!generic;
618
741
  }
619
- const messages = extractMessages(node, settings);
742
+ const messages = extractMessages({
743
+ ...node,
744
+ arguments: [descriptor]
745
+ }, settings);
620
746
  const message = messages[0]?.[0];
621
747
  if (messages.length !== 1 || !message || typeof message.message.defaultMessage !== "string" || !staticMessage(message.messageNode ?? void 0)) {
622
748
  if (generic) context.report({
@@ -692,7 +818,7 @@ const rule$20 = {
692
818
  contract: "Generate or refresh the ICU argument contract.",
693
819
  invalid: "Cannot generate message types: {{error}}.",
694
820
  manual: "Unsupported number of generic arguments; update the call manually.",
695
- annotation: "Catalog annotation may erase ICU contracts; use per-message TypedMessageDescriptor types.",
821
+ annotation: "Message annotation may erase ICU contracts; use TypedMessageDescriptor types.",
696
822
  dynamic: "Typed messages require static message strings and parser options for contract verification."
697
823
  }
698
824
  },
@@ -782,36 +908,63 @@ const rule$20 = {
782
908
  const typed = staticObject(options) && options.properties.length === 1 && options.properties.some((p) => p.type === "Property" && (p.key.type === "Identifier" ? p.key.name : p.key.type === "Literal" ? p.key.value : void 0) === "typed" && p.value.type === "Literal" && p.value.value === true);
783
909
  if (!generic && !typed && !context.options[0]?.generateTypes) return;
784
910
  if (node.arguments.length > 2 || options && !typed) return;
785
- const descriptor = node.arguments[0];
786
- if (!staticObject(descriptor)) {
911
+ const descriptor = resolveDescriptor(context, node.arguments[0]);
912
+ const catalog = imported.helper === "defineMessages";
913
+ if (descriptor?.type !== "ObjectExpression" || !descriptor.properties.every((p) => p.type === "Property" && p.kind === "init" && !p.method && (!p.computed || !!typeQuery(p.key)))) {
787
914
  if (generic || typed) context.report({
788
915
  node,
789
916
  messageId: "dynamic"
790
917
  });
791
918
  return;
792
919
  }
793
- if (imported.helper === "defineMessages" && !descriptor.properties.every((p) => p.type === "Property" && staticObject(p.value))) {
794
- if (generic || typed) context.report({
795
- node,
796
- messageId: "dynamic"
797
- });
798
- return;
799
- }
800
- const messages = extractMessages({
801
- ...node,
802
- callee: {
803
- type: "Identifier",
804
- name: imported.helper
920
+ const entries = catalog ? descriptor.properties.map((p) => {
921
+ if (p.type !== "Property") return void 0;
922
+ return {
923
+ value: resolveDescriptor(context, p.value),
924
+ reference: typeQuery(p.value),
925
+ key: p.computed ? "[" + typeQuery(p.key) + "]" : JSON.stringify(p.key.type === "Identifier" ? p.key.name : p.key.type === "Literal" ? String(p.key.value) : "")
926
+ };
927
+ }) : [{
928
+ value: descriptor,
929
+ reference: void 0,
930
+ key: ""
931
+ }];
932
+ const types = [];
933
+ for (const entry of entries) {
934
+ if (entry && staticObject(entry.value)) {
935
+ const messages = extractMessages({
936
+ ...node,
937
+ callee: {
938
+ type: "Identifier",
939
+ name: "defineMessage"
940
+ },
941
+ arguments: [entry.value]
942
+ }, getSettings(context));
943
+ const message = messages[0]?.[0];
944
+ if (messages.length === 1 && typeof message?.message.defaultMessage === "string" && staticMessage(message.messageNode ?? void 0)) {
945
+ try {
946
+ types.push(messageTypes(parse(message.message.defaultMessage, { ignoreTag: getSettings(context).ignoreTag }), imported.module, context.options[0]?.ignoreList));
947
+ } catch (error) {
948
+ context.report({
949
+ node,
950
+ messageId: "invalid",
951
+ data: { error: error instanceof Error ? error.message : String(error) }
952
+ });
953
+ return;
954
+ }
955
+ continue;
956
+ }
957
+ } else if (catalog && entry?.reference) {
958
+ types.push("import(" + JSON.stringify(imported.module) + ").MessageValuesOf<typeof " + entry.reference + ">");
959
+ continue;
805
960
  }
806
- }, getSettings(context));
807
- if (messages.some(([message]) => typeof message.message.defaultMessage !== "string" || !staticMessage(message.messageNode ?? void 0)) || messages.length !== (imported.helper === "defineMessage" ? 1 : descriptor.properties.length)) {
808
961
  if (generic || typed) context.report({
809
962
  node,
810
963
  messageId: "dynamic"
811
964
  });
812
965
  return;
813
966
  }
814
- const annotation = imported.helper === "defineMessages" ? catalogAnnotation(context, node) : void 0;
967
+ const annotation = imported.helper === "defineMessages" || imported.helper === "defineMessage" ? messageAnnotation(context, node, catalog) : void 0;
815
968
  if (annotation && !annotation.supported) {
816
969
  context.report({
817
970
  node: annotation.annotation,
@@ -822,22 +975,8 @@ const rule$20 = {
822
975
  let contract;
823
976
  let catalogType;
824
977
  try {
825
- const types = messages.map(([message]) => messageTypes(parse(message.message.defaultMessage, { ignoreTag: getSettings(context).ignoreTag }), imported.module, context.options[0]?.ignoreList));
826
- if (annotation) {
827
- catalogType = "{ " + descriptor.properties.map((property, index) => {
828
- if (property.type !== "Property") throw new Error("Unexpected spread");
829
- const key = property.key.type === "Identifier" ? property.key.name : property.key.type === "Literal" ? String(property.key.value) : void 0;
830
- if (key === void 0) throw new Error("Unsupported catalog key");
831
- return "readonly " + JSON.stringify(key) + ": import(" + JSON.stringify(imported.module) + ").TypedMessageDescriptor<" + types[index] + ">";
832
- }).join("; ") + " }";
833
- if (!descriptor.properties.length) catalogType = "{}";
834
- }
835
- contract = imported.helper === "defineMessage" ? types[0] : descriptor.properties.length === 0 ? "{}" : `{ ${descriptor.properties.map((p, i) => {
836
- if (p.type !== "Property") throw new Error("Unexpected spread");
837
- const key = p.key.type === "Identifier" ? p.key.name : p.key.type === "Literal" ? String(p.key.value) : void 0;
838
- if (key === void 0) throw new Error("Unsupported catalog key");
839
- return `readonly ${JSON.stringify(key)}: ${types[i]}`;
840
- }).join("; ")} }`;
978
+ if (annotation) catalogType = !catalog ? "import(" + JSON.stringify(imported.module) + ").TypedMessageDescriptor<" + types[0] + ">" : entries.length ? "{ " + entries.map((entry, index) => "readonly " + entry.key + ": import(" + JSON.stringify(imported.module) + ").TypedMessageDescriptor<" + types[index] + ">").join("; ") + " }" : "{}";
979
+ contract = !catalog ? types[0] : entries.length ? "{ " + entries.map((entry, index) => "readonly " + entry.key + ": " + types[index]).join("; ") + " }" : "{}";
841
980
  } catch (error) {
842
981
  context.report({
843
982
  node,
@@ -863,7 +1002,11 @@ const rule$20 = {
863
1002
  node,
864
1003
  messageId: "contract",
865
1004
  fix(fixer) {
866
- 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)];
1005
+ const edits = [
1006
+ ...imports.fix(fixer),
1007
+ ...obsoleteTypes(context, [...parameters?.[0] ? [parameters[0]] : [], ...annotation && !annotationMatches ? [annotation.annotation] : []], contract + (catalogType ?? ""), fixer),
1008
+ 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)
1009
+ ];
867
1010
  if (annotation && !annotationMatches) edits.push(fixer.replaceText(annotation.annotation, catalogType));
868
1011
  return edits;
869
1012
  }
@@ -5122,7 +5265,7 @@ var package_exports = /* @__PURE__ */ __exportAll({
5122
5265
  version: () => version$1
5123
5266
  });
5124
5267
  var name$1 = "eslint-plugin-formatjs";
5125
- var version$1 = "8.0.0";
5268
+ var version$1 = "8.0.2";
5126
5269
  var description = "ESLint plugin for formatjs";
5127
5270
  var keywords = [
5128
5271
  "eslint",