@blumintinc/eslint-plugin-blumint 1.20.170 → 1.20.172

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 (47) hide show
  1. package/lib/index.js +1 -1
  2. package/lib/rules/enforce-assert-safe-object-key.js +78 -3
  3. package/lib/rules/enforce-dynamic-firebase-imports.js +587 -52
  4. package/lib/rules/enforce-early-destructuring.js +181 -51
  5. package/lib/rules/enforce-empty-object-check.d.ts +1 -0
  6. package/lib/rules/enforce-empty-object-check.js +686 -1
  7. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +138 -12
  8. package/lib/rules/enforce-firestore-doc-ref-generic.js +101 -21
  9. package/lib/rules/enforce-firestore-set-merge.js +220 -20
  10. package/lib/rules/enforce-m3-sentence-case.js +39 -5
  11. package/lib/rules/enforce-memoize-async.js +12 -0
  12. package/lib/rules/enforce-memoize-getters.js +20 -0
  13. package/lib/rules/enforce-stable-hash-spread-props.js +296 -13
  14. package/lib/rules/ensure-pointer-events-none.d.ts +11 -1
  15. package/lib/rules/ensure-pointer-events-none.js +231 -14
  16. package/lib/rules/fast-deep-equal-over-microdiff.js +30 -2
  17. package/lib/rules/flatten-push-calls.js +97 -38
  18. package/lib/rules/jsdoc-above-field.js +47 -19
  19. package/lib/rules/memo-compare-deeply-complex-props.js +238 -0
  20. package/lib/rules/no-explicit-return-type.js +35 -0
  21. package/lib/rules/no-jsx-whitespace-literal.js +47 -0
  22. package/lib/rules/no-useless-usememo-primitives.js +204 -20
  23. package/lib/rules/parallelize-async-operations.d.ts +1 -0
  24. package/lib/rules/parallelize-async-operations.js +216 -15
  25. package/lib/rules/prefer-clone-deep.js +133 -2
  26. package/lib/rules/prefer-next-dynamic.js +45 -14
  27. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +239 -27
  28. package/lib/rules/prefer-type-over-interface.d.ts +10 -2
  29. package/lib/rules/prefer-type-over-interface.js +238 -13
  30. package/lib/rules/prefer-use-deep-compare-memo.d.ts +6 -1
  31. package/lib/rules/prefer-use-deep-compare-memo.js +184 -7
  32. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +167 -50
  33. package/lib/rules/require-props-composition.js +257 -0
  34. package/lib/rules/use-latest-callback.js +11 -2
  35. package/lib/utils/arrowAnnotationGap.js +227 -10
  36. package/lib/utils/importRemoval.d.ts +33 -4
  37. package/lib/utils/importRemoval.js +141 -9
  38. package/lib/utils/inlineExpressionParens.d.ts +30 -0
  39. package/lib/utils/inlineExpressionParens.js +465 -0
  40. package/lib/utils/reindentRelocated.d.ts +7 -0
  41. package/lib/utils/reindentRelocated.js +108 -0
  42. package/lib/utils/replacementSegments.d.ts +5 -0
  43. package/lib/utils/replacementSegments.js +2 -1
  44. package/lib/utils/resourceHandleType.d.ts +21 -0
  45. package/lib/utils/resourceHandleType.js +217 -0
  46. package/package.json +1 -1
  47. package/release-manifest.json +243 -0
package/lib/index.js CHANGED
@@ -223,7 +223,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
223
223
  module.exports = {
224
224
  meta: {
225
225
  name: '@blumintinc/eslint-plugin-blumint',
226
- version: '1.20.170',
226
+ version: '1.20.172',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -640,6 +640,47 @@ function foldKeyDomains(domains) {
640
640
  }
641
641
  return values;
642
642
  }
643
+ /**
644
+ * The span of the access a written key belongs to: `obj[key]` including the
645
+ * object and both brackets, `[key]` for a computed property (whose value is an
646
+ * expression of its own), and the whole comparison for `key in obj`. Null where
647
+ * the key sits in none of those, which leaves the wrap to span the key alone.
648
+ *
649
+ * This is the unit a fix that rewrites a key has to claim. The key's own range
650
+ * stops short of the `]` that closes the access, and a fixer that reformats the
651
+ * access spreads its edits across the whole of it — so a span ending between
652
+ * the key and its bracket splits that set in half. Claiming the access instead
653
+ * leaves a competing rewrite of it either wholly discarded (and re-made against
654
+ * the fixed text on a later pass) or wholly applied, both of which parse.
655
+ */
656
+ function accessSpan(sourceCode, written) {
657
+ const { parent } = written;
658
+ if (!parent) {
659
+ return null;
660
+ }
661
+ if (parent.type === utils_1.AST_NODE_TYPES.MemberExpression &&
662
+ parent.computed &&
663
+ parent.property === written) {
664
+ return [parent.range[0], parent.range[1]];
665
+ }
666
+ if (parent.type === utils_1.AST_NODE_TYPES.Property &&
667
+ parent.computed &&
668
+ parent.key === written) {
669
+ // A computed property's range runs past its key to the end of its value,
670
+ // which the wrap has no business claiming; the bracket that closes the key
671
+ // is where this access ends.
672
+ const closing = sourceCode.getTokenAfter(written);
673
+ return closing?.value === ']'
674
+ ? [parent.range[0], closing.range[1]]
675
+ : [written.range[0], written.range[1]];
676
+ }
677
+ if (parent.type === utils_1.AST_NODE_TYPES.BinaryExpression &&
678
+ parent.operator === 'in' &&
679
+ parent.left === written) {
680
+ return [parent.range[0], parent.range[1]];
681
+ }
682
+ return null;
683
+ }
643
684
  /**
644
685
  * An enum is a compiler-checked finite set. Members with literal initializers
645
686
  * enumerate their runtime key strings (which is what lets the forbidden-name
@@ -836,17 +877,51 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
836
877
  const importStatement = `import { assertSafe } from '${computeImportSpecifier()}';\n`;
837
878
  return (0, importInsertion_1.insertAtImportAnchor)(context.sourceCode, fixer, (0, importInsertion_1.importInsertionAnchor)(context.sourceCode), importStatement);
838
879
  };
880
+ /**
881
+ * The wrap of the key, emitted over the whole access the key belongs to
882
+ * rather than over the key alone.
883
+ *
884
+ * ESLint merges the fixes of one report into a single edit spanning
885
+ * [first start, last end], splicing the original text back in between. The
886
+ * import anchor sits at the top of the file, so bundling it with the wrap
887
+ * turns a three-character edit into one that claims everything from the
888
+ * file's first statement to the end of the key — which is a point in the
889
+ * middle of the access, before the `]` that closes it. That span sorts
890
+ * ahead of every competing fix and wins the race against all of them, but
891
+ * only up to its end. A fixer whose edits are coherent only as a set (a
892
+ * formatter re-wrapping one access across several lines is the common
893
+ * case) then has the edits inside the span discarded and the edits past
894
+ * its end applied, and the two halves do not fit together: the emitted
895
+ * file does not parse.
896
+ *
897
+ * Ending the span where the access ends puts every edit of such a
898
+ * competing rewrite on one side of the boundary — either wholly inside the
899
+ * span (discarded whole, and re-made against the fixed text on the next
900
+ * pass) or wholly outside it. Both parse. The re-emitted head and tail are
901
+ * copied verbatim from the source, so the text this fix produces is
902
+ * character-for-character what replacing the key alone produced.
903
+ */
904
+ const wrapKey = (fixer, node, argText) => {
905
+ const replacement = `assertSafe(${argText})`;
906
+ const span = accessSpan(context.sourceCode, node);
907
+ if (!span) {
908
+ return fixer.replaceText(node, replacement);
909
+ }
910
+ const [start, end] = span;
911
+ const { text } = context.sourceCode;
912
+ return fixer.replaceTextRange([start, end], `${text.slice(start, node.range[0])}${replacement}${text.slice(node.range[1], end)}`);
913
+ };
839
914
  /**
840
915
  * Helper function to create fixes for a node
841
916
  */
842
917
  const createFixes = (fixer, node, argText) => {
843
918
  const fixes = [];
844
- if (!importClaimed && !importsAssertSafe(context.sourceCode.ast)) {
919
+ const carriesImport = !importClaimed && !importsAssertSafe(context.sourceCode.ast);
920
+ if (carriesImport) {
845
921
  fixes.push(addAssertSafeImport(fixer));
846
922
  importClaimed = true;
847
923
  }
848
- // Replace the node with assertSafe(argText)
849
- fixes.push(fixer.replaceText(node, `assertSafe(${argText})`));
924
+ fixes.push(wrapKey(fixer, node, argText));
850
925
  return fixes;
851
926
  };
852
927
  // The report is emitted even when suppressed: ESLint discards it, and