All files / src/rules sorted.ts

100% Statements 28/28
100% Branches 10/10
100% Functions 5/5
100% Lines 28/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93  4x   4x 4x             4x         4x                   19x   19x         19x   19x 19x 1x     18x   18x 6x 6x 6x         6x 31x     6x     6x       23x   23x 1x 1x     23x     6x         6x           6x                    
import { Rule } from "eslint";
import { messages } from "../constants/messages";
import { Program, ImportDeclaration } from "estree";
import { nodesArrayToText, getNodeEndPosition } from "../services/eslint";
import {
  getFirstNotSorted,
  isImportDeclaration,
  createCalculateSortIndex,
  getImportsWithNodesBetween,
} from "../services/imports";
 
const opts = {
  DISABLE_LINE_SORTS: "no-line-length-sort",
  SORT_BY_SPECIFIER: "sort-by-specifiers-length",
};
 
export default {
  meta: {
    fixable: "code",
  },
  schema: [
    {
      enum: [opts.DISABLE_LINE_SORTS, opts.SORT_BY_SPECIFIER],
    },
  ],
  create: (context: Rule.RuleContext) => {
    const sourceCode = context.getSourceCode();
 
    const calculateSortIndex = createCalculateSortIndex(sourceCode, {
      sortBySpecifier: context.options.includes(opts.SORT_BY_SPECIFIER),
      disableLineSorts: context.options.includes(opts.DISABLE_LINE_SORTS),
    });
 
    return {
      Program: (program: Program) => {
        const imports = getImportsWithNodesBetween(program);
        if (!imports.length) {
          return;
        }
 
        const firstNotSorted = getFirstNotSorted(imports, calculateSortIndex);
 
        if (firstNotSorted) {
          const autoFix = (fixer: Rule.RuleFixer) => {
            const importsStart = imports[0].range![0];
            const importsEnd = getNodeEndPosition(
              sourceCode,
              imports[imports.length - 1]
            );
 
            const sortedImports = (imports as ImportDeclaration[]).sort(
              (a, b) => calculateSortIndex(a) - calculateSortIndex(b)
            );
 
            let metNonImportNode = false;
 
            // do not add additional \n to the end of imports
            const addSeparatorBetweenNodes = (
              source: string,
              index: number
            ) => {
              const node = sortedImports[index];
 
              if (!metNonImportNode && !isImportDeclaration(node)) {
                metNonImportNode = true;
                source = "\n" + source;
              }
 
              return index < sortedImports.length - 1 ? source + "\n" : source;
            };
 
            const sortedImportsText = nodesArrayToText(sourceCode)(
              sortedImports,
              addSeparatorBetweenNodes
            );
 
            return fixer.replaceTextRange(
              [importsStart, importsEnd],
              sortedImportsText
            );
          };
 
          context.report({
            fix: autoFix,
            loc: firstNotSorted.loc!,
            message: messages.NOT_SORTED,
          });
        }
      },
    } as Rule.RuleListener;
  },
} as Rule.RuleModule;