eslint-plugin-formatjs 8.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 +189 -48
  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({
@@ -782,29 +906,56 @@ const rule$20 = {
782
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);
783
907
  if (!generic && !typed && !context.options[0]?.generateTypes) return;
784
908
  if (node.arguments.length > 2 || options && !typed) return;
785
- const descriptor = node.arguments[0];
786
- if (!staticObject(descriptor)) {
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)))) {
787
912
  if (generic || typed) context.report({
788
913
  node,
789
914
  messageId: "dynamic"
790
915
  });
791
916
  return;
792
917
  }
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
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;
805
958
  }
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
959
  if (generic || typed) context.report({
809
960
  node,
810
961
  messageId: "dynamic"
@@ -822,22 +973,8 @@ const rule$20 = {
822
973
  let contract;
823
974
  let catalogType;
824
975
  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("; ")} }`;
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("; ") + " }" : "{}";
841
978
  } catch (error) {
842
979
  context.report({
843
980
  node,
@@ -863,7 +1000,11 @@ const rule$20 = {
863
1000
  node,
864
1001
  messageId: "contract",
865
1002
  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)];
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
+ ];
867
1008
  if (annotation && !annotationMatches) edits.push(fixer.replaceText(annotation.annotation, catalogType));
868
1009
  return edits;
869
1010
  }
@@ -5122,7 +5263,7 @@ var package_exports = /* @__PURE__ */ __exportAll({
5122
5263
  version: () => version$1
5123
5264
  });
5124
5265
  var name$1 = "eslint-plugin-formatjs";
5125
- var version$1 = "8.0.0";
5266
+ var version$1 = "8.0.1";
5126
5267
  var description = "ESLint plugin for formatjs";
5127
5268
  var keywords = [
5128
5269
  "eslint",