eslint-plugin-formatjs 7.0.0 → 8.0.1

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 +204 -58
  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,6 +575,70 @@ function hoistTypes(context, node, contract) {
527
575
  }
528
576
  };
529
577
  }
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
+ }
530
642
  function catalogAnnotation(context, node) {
531
643
  const parent = node.parent;
532
644
  if (parent?.type !== "VariableDeclarator" || parent.init !== node) return;
@@ -534,7 +646,7 @@ function catalogAnnotation(context, node) {
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,19 +657,28 @@ 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 (type.type === "TSTypeLiteral") return !!type.members?.every((member) => member.type === "TSPropertySignature" && !!member.typeAnnotation && descriptorType(member.typeAnnotation.typeAnnotation));
667
+ let scope = context.sourceCode.getScope(type);
551
668
  while (scope) {
552
- if (scope.set.get(type.typeName?.name ?? "")?.defs.length) return false;
669
+ const binding = scope.set.get(type.typeName?.name ?? "");
670
+ if (binding?.defs.length) {
671
+ const declaration = binding.defs[0].node;
672
+ return declaration.type === "TSTypeAliasDeclaration" && !declaration.typeParameters && !!declaration.typeAnnotation && broad(declaration.typeAnnotation, seen);
673
+ }
553
674
  scope = scope.upper;
554
675
  }
555
676
  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");
677
+ if (type.typeName?.name === "Readonly" && parameters?.length === 1) return broad(parameters[0], seen);
678
+ return type.typeName?.name === "Record" && parameters?.length === 2 && descriptorType(parameters[1]);
558
679
  }
559
680
  function typedMap(type) {
560
- return type.type === "TSTypeLiteral" && !!type.members?.every((member) => member.type === "TSPropertySignature" && !member.computed && !!member.typeAnnotation && imported(member.typeAnnotation.typeAnnotation, "TypedMessageDescriptor"));
681
+ return type.type === "TSTypeLiteral" && !!type.members?.every((member) => member.type === "TSPropertySignature" && !!member.typeAnnotation && imported(member.typeAnnotation.typeAnnotation, "TypedMessageDescriptor"));
561
682
  }
562
683
  let base = annotation;
563
684
  let generated = typedMap(annotation) ? annotation : void 0;
@@ -598,7 +719,7 @@ function checkInlineMessage(context, node) {
598
719
  const generic = call.typeArguments ?? call.typeParameters;
599
720
  if (!generic && !context.options[0]?.generateTypes) return;
600
721
  const generated = !!generic && /^<\s*\/\* @formatjs-generated \*\//.test(source.getText(generic));
601
- const descriptor = node.arguments[0];
722
+ const descriptor = resolveDescriptor(context, node.arguments[0]);
602
723
  if (!staticObject(descriptor)) {
603
724
  if (generic) context.report({
604
725
  node,
@@ -616,7 +737,10 @@ function checkInlineMessage(context, node) {
616
737
  });
617
738
  return !!generic;
618
739
  }
619
- const messages = extractMessages(node, settings);
740
+ const messages = extractMessages({
741
+ ...node,
742
+ arguments: [descriptor]
743
+ }, settings);
620
744
  const message = messages[0]?.[0];
621
745
  if (messages.length !== 1 || !message || typeof message.message.defaultMessage !== "string" || !staticMessage(message.messageNode ?? void 0)) {
622
746
  if (generic) context.report({
@@ -639,6 +763,17 @@ function checkInlineMessage(context, node) {
639
763
  const imports = hoistTypes(context, node, contract);
640
764
  contract = imports.text;
641
765
  const parameters = generic?.params;
766
+ if (contract === "{}" && node.arguments.length === 1) {
767
+ if (!generic) return true;
768
+ if (parameters?.length === 1) {
769
+ context.report({
770
+ node: generic,
771
+ messageId: "contract",
772
+ fix: (fixer) => fixer.remove(generic)
773
+ });
774
+ return true;
775
+ }
776
+ }
642
777
  if (parameters && parameters.length >= 1 && parameters.length <= 2 && renderType(parameters[0]) === contract && !generated) return true;
643
778
  if (generic && (!parameters || parameters.length > 2)) {
644
779
  context.report({
@@ -769,33 +904,59 @@ const rule$20 = {
769
904
  const generated = !!generic && /^<\s*\/\* @formatjs-generated \*\//.test(source.getText(generic));
770
905
  const options = node.arguments[1];
771
906
  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);
772
- if (!typed && !generated && !context.options[0]?.generateTypes) return;
773
- if (!typed && generic && !generated && !context.options[0]?.generateTypes) return;
907
+ if (!generic && !typed && !context.options[0]?.generateTypes) return;
774
908
  if (node.arguments.length > 2 || options && !typed) return;
775
- const descriptor = node.arguments[0];
776
- if (!staticObject(descriptor)) {
777
- if (typed || generated) context.report({
778
- node,
779
- messageId: "dynamic"
780
- });
781
- return;
782
- }
783
- if (imported.helper === "defineMessages" && !descriptor.properties.every((p) => p.type === "Property" && staticObject(p.value))) {
784
- if (typed || generated) context.report({
909
+ const descriptor = resolveDescriptor(context, node.arguments[0]);
910
+ const catalog = imported.helper === "defineMessages";
911
+ if (descriptor?.type !== "ObjectExpression" || !descriptor.properties.every((p) => p.type === "Property" && p.kind === "init" && !p.method && (!p.computed || !!typeQuery(p.key)))) {
912
+ if (generic || typed) context.report({
785
913
  node,
786
914
  messageId: "dynamic"
787
915
  });
788
916
  return;
789
917
  }
790
- const messages = extractMessages({
791
- ...node,
792
- callee: {
793
- type: "Identifier",
794
- name: imported.helper
918
+ const entries = catalog ? descriptor.properties.map((p) => {
919
+ if (p.type !== "Property") return void 0;
920
+ return {
921
+ value: resolveDescriptor(context, p.value),
922
+ reference: typeQuery(p.value),
923
+ key: p.computed ? "[" + typeQuery(p.key) + "]" : JSON.stringify(p.key.type === "Identifier" ? p.key.name : p.key.type === "Literal" ? String(p.key.value) : "")
924
+ };
925
+ }) : [{
926
+ value: descriptor,
927
+ reference: void 0,
928
+ key: ""
929
+ }];
930
+ const types = [];
931
+ for (const entry of entries) {
932
+ if (entry && staticObject(entry.value)) {
933
+ const messages = extractMessages({
934
+ ...node,
935
+ callee: {
936
+ type: "Identifier",
937
+ name: "defineMessage"
938
+ },
939
+ arguments: [entry.value]
940
+ }, getSettings(context));
941
+ const message = messages[0]?.[0];
942
+ if (messages.length === 1 && typeof message?.message.defaultMessage === "string" && staticMessage(message.messageNode ?? void 0)) {
943
+ try {
944
+ types.push(messageTypes(parse(message.message.defaultMessage, { ignoreTag: getSettings(context).ignoreTag }), imported.module, context.options[0]?.ignoreList));
945
+ } catch (error) {
946
+ context.report({
947
+ node,
948
+ messageId: "invalid",
949
+ data: { error: error instanceof Error ? error.message : String(error) }
950
+ });
951
+ return;
952
+ }
953
+ continue;
954
+ }
955
+ } else if (catalog && entry?.reference) {
956
+ types.push("import(" + JSON.stringify(imported.module) + ").MessageValuesOf<typeof " + entry.reference + ">");
957
+ continue;
795
958
  }
796
- }, getSettings(context));
797
- if (messages.some(([message]) => typeof message.message.defaultMessage !== "string" || !staticMessage(message.messageNode ?? void 0)) || messages.length !== (imported.helper === "defineMessage" ? 1 : descriptor.properties.length)) {
798
- if (typed || generated) context.report({
959
+ if (generic || typed) context.report({
799
960
  node,
800
961
  messageId: "dynamic"
801
962
  });
@@ -812,22 +973,8 @@ const rule$20 = {
812
973
  let contract;
813
974
  let catalogType;
814
975
  try {
815
- const types = messages.map(([message]) => messageTypes(parse(message.message.defaultMessage, { ignoreTag: getSettings(context).ignoreTag }), imported.module, context.options[0]?.ignoreList));
816
- if (annotation) {
817
- catalogType = "{ " + descriptor.properties.map((property, index) => {
818
- if (property.type !== "Property") throw new Error("Unexpected spread");
819
- const key = property.key.type === "Identifier" ? property.key.name : property.key.type === "Literal" ? String(property.key.value) : void 0;
820
- if (key === void 0) throw new Error("Unsupported catalog key");
821
- return "readonly " + JSON.stringify(key) + ": import(" + JSON.stringify(imported.module) + ").TypedMessageDescriptor<" + types[index] + ">";
822
- }).join("; ") + " }";
823
- if (!descriptor.properties.length) catalogType = "{}";
824
- }
825
- contract = imported.helper === "defineMessage" ? types[0] : descriptor.properties.length === 0 ? "{}" : `{ ${descriptor.properties.map((p, i) => {
826
- if (p.type !== "Property") throw new Error("Unexpected spread");
827
- const key = p.key.type === "Identifier" ? p.key.name : p.key.type === "Literal" ? String(p.key.value) : void 0;
828
- if (key === void 0) throw new Error("Unsupported catalog key");
829
- return `readonly ${JSON.stringify(key)}: ${types[i]}`;
830
- }).join("; ")} }`;
976
+ if (annotation) catalogType = entries.length ? "{ " + entries.map((entry, index) => "readonly " + entry.key + ": import(" + JSON.stringify(imported.module) + ").TypedMessageDescriptor<" + types[index] + ">").join("; ") + " }" : "{}";
977
+ contract = !catalog ? types[0] : entries.length ? "{ " + entries.map((entry, index) => "readonly " + entry.key + ": " + types[index]).join("; ") + " }" : "{}";
831
978
  } catch (error) {
832
979
  context.report({
833
980
  node,
@@ -841,7 +988,7 @@ const rule$20 = {
841
988
  const annotationMatches = !annotation || annotation.annotation === annotation.generated && renderType(annotation.generated) === catalogType;
842
989
  const expected = `<${contract}>`;
843
990
  const parameters = generic?.params;
844
- if (parameters && parameters.length >= 1 && parameters.length <= 2 && renderType(parameters[0]) === contract && typed && !generated && annotationMatches) return;
991
+ if (parameters && parameters.length >= 1 && parameters.length <= 2 && renderType(parameters[0]) === contract && !generated && annotationMatches) return;
845
992
  if (generic && (!parameters || parameters.length < 1 || parameters.length > 2)) {
846
993
  context.report({
847
994
  node: generic,
@@ -853,13 +1000,12 @@ const rule$20 = {
853
1000
  node,
854
1001
  messageId: "contract",
855
1002
  fix(fixer) {
856
- 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)];
1003
+ const edits = [
1004
+ ...imports.fix(fixer),
1005
+ ...obsoleteTypes(context, [...parameters?.[0] ? [parameters[0]] : [], ...annotation && !annotationMatches ? [annotation.annotation] : []], contract + (catalogType ?? ""), fixer),
1006
+ 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)
1007
+ ];
857
1008
  if (annotation && !annotationMatches) edits.push(fixer.replaceText(annotation.annotation, catalogType));
858
- if (!typed) {
859
- const close = source.getLastToken(node);
860
- const previous = source.getTokenBefore(close);
861
- edits.push(fixer.insertTextBefore(close, `${previous.value === "," ? "" : ","} {typed: true}`));
862
- }
863
1009
  return edits;
864
1010
  }
865
1011
  });
@@ -5117,7 +5263,7 @@ var package_exports = /* @__PURE__ */ __exportAll({
5117
5263
  version: () => version$1
5118
5264
  });
5119
5265
  var name$1 = "eslint-plugin-formatjs";
5120
- var version$1 = "7.0.0";
5266
+ var version$1 = "8.0.1";
5121
5267
  var description = "ESLint plugin for formatjs";
5122
5268
  var keywords = [
5123
5269
  "eslint",