• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import { TSESTree } from '@typescript-eslint/experimental-utils';
2import * as util from '../util';
3
4export type Options = [
5  {
6    ignoreStringArrays?: boolean;
7  },
8];
9export type MessageIds = 'requireCompare';
10
11export default util.createRule<Options, MessageIds>({
12  name: 'require-array-sort-compare',
13  defaultOptions: [
14    {
15      ignoreStringArrays: false,
16    },
17  ],
18
19  meta: {
20    type: 'problem',
21    docs: {
22      description:
23        'Requires `Array#sort` calls to always provide a `compareFunction`',
24      category: 'Best Practices',
25      recommended: false,
26      requiresTypeChecking: true,
27    },
28    messages: {
29      requireCompare: "Require 'compare' argument.",
30    },
31    schema: [
32      {
33        type: 'object',
34        properties: {
35          ignoreStringArrays: {
36            type: 'boolean',
37          },
38        },
39      },
40    ],
41  },
42
43  create(context, [options]) {
44    const service = util.getParserServices(context);
45    const checker = service.program.getTypeChecker();
46
47    /**
48     * Check if a given node is an array which all elements are string.
49     * @param node
50     */
51    function isStringArrayNode(node: TSESTree.LeftHandSideExpression): boolean {
52      const type = checker.getTypeAtLocation(
53        service.esTreeNodeToTSNodeMap.get(node),
54      );
55      if (checker.isArrayType(type) || checker.isTupleType(type)) {
56        const typeArgs = checker.getTypeArguments(type);
57        return typeArgs.every(
58          arg => util.getTypeName(checker, arg) === 'string',
59        );
60      }
61      return false;
62    }
63
64    return {
65      "CallExpression[arguments.length=0] > MemberExpression[property.name='sort'][computed=false]"(
66        callee: TSESTree.MemberExpression,
67      ): void {
68        const tsNode = service.esTreeNodeToTSNodeMap.get(callee.object);
69        const calleeObjType = util.getConstrainedTypeAtLocation(
70          checker,
71          tsNode,
72        );
73
74        if (options.ignoreStringArrays && isStringArrayNode(callee.object)) {
75          return;
76        }
77
78        if (util.isTypeArrayTypeOrUnionOfArrayTypes(calleeObjType, checker)) {
79          context.report({ node: callee.parent!, messageId: 'requireCompare' });
80        }
81      },
82    };
83  },
84});
85