1 | 'use strict';
|
2 | const getDocumentationUrl = require('./utils/get-documentation-url');
|
3 |
|
4 | const tcIdentifiers = new Set([
|
5 | 'isArguments',
|
6 | 'isArray',
|
7 | 'isArrayBuffer',
|
8 | 'isArrayLike',
|
9 | 'isArrayLikeObject',
|
10 | 'isBigInt',
|
11 | 'isBoolean',
|
12 | 'isBuffer',
|
13 | 'isDate',
|
14 | 'isElement',
|
15 | 'isError',
|
16 | 'isFinite',
|
17 | 'isFunction',
|
18 | 'isInteger',
|
19 | 'isLength',
|
20 | 'isMap',
|
21 | 'isNaN',
|
22 | 'isNative',
|
23 | 'isNil',
|
24 | 'isNull',
|
25 | 'isNumber',
|
26 | 'isObject',
|
27 | 'isObjectLike',
|
28 | 'isPlainObject',
|
29 | 'isPrototypeOf',
|
30 | 'isRegExp',
|
31 | 'isSafeInteger',
|
32 | 'isSet',
|
33 | 'isString',
|
34 | 'isSymbol',
|
35 | 'isTypedArray',
|
36 | 'isUndefined',
|
37 | 'isView',
|
38 | 'isWeakMap',
|
39 | 'isWeakSet',
|
40 | 'isWindow',
|
41 | 'isXMLDoc'
|
42 | ]);
|
43 |
|
44 | const tcGlobalIdentifiers = new Set([
|
45 | 'isNaN',
|
46 | 'isFinite'
|
47 | ]);
|
48 |
|
49 | const isTypecheckingIdentifier = (node, callExpression, isMemberExpression) =>
|
50 | callExpression !== undefined &&
|
51 | callExpression.arguments.length > 0 &&
|
52 | node.type === 'Identifier' &&
|
53 | ((isMemberExpression === true &&
|
54 | tcIdentifiers.has(node.name)) ||
|
55 | (isMemberExpression === false &&
|
56 | tcGlobalIdentifiers.has(node.name)));
|
57 |
|
58 | const throwsErrorObject = node =>
|
59 | node.argument.type === 'NewExpression' &&
|
60 | node.argument.callee.type === 'Identifier' &&
|
61 | node.argument.callee.name === 'Error';
|
62 |
|
63 | const isLone = node => node.parent && node.parent.body && node.parent.body.length === 1;
|
64 |
|
65 | const isTypecheckingMemberExpression = (node, callExpression) => {
|
66 | if (isTypecheckingIdentifier(node.property, callExpression, true)) {
|
67 | return true;
|
68 | }
|
69 |
|
70 | if (node.object.type === 'MemberExpression') {
|
71 | return isTypecheckingMemberExpression(node.object, callExpression);
|
72 | }
|
73 |
|
74 | return false;
|
75 | };
|
76 |
|
77 | const isTypecheckingExpression = (node, callExpression) => {
|
78 | switch (node.type) {
|
79 | case 'Identifier':
|
80 | return isTypecheckingIdentifier(node, callExpression, false);
|
81 | case 'MemberExpression':
|
82 | return isTypecheckingMemberExpression(node, callExpression);
|
83 | case 'CallExpression':
|
84 | return isTypecheckingExpression(node.callee, node);
|
85 | case 'UnaryExpression':
|
86 | return (
|
87 | node.operator === 'typeof' ||
|
88 | (node.operator === '!' && isTypecheckingExpression(node.argument))
|
89 | );
|
90 | case 'BinaryExpression':
|
91 | return (
|
92 | node.operator === 'instanceof' ||
|
93 | isTypecheckingExpression(node.left, callExpression) ||
|
94 | isTypecheckingExpression(node.right, callExpression)
|
95 | );
|
96 | case 'LogicalExpression':
|
97 | return (
|
98 | isTypecheckingExpression(node.left, callExpression) &&
|
99 | isTypecheckingExpression(node.right, callExpression)
|
100 | );
|
101 | default:
|
102 | return false;
|
103 | }
|
104 | };
|
105 |
|
106 | const isTypechecking = node => node.type === 'IfStatement' && isTypecheckingExpression(node.test);
|
107 |
|
108 | const create = context => {
|
109 | return {
|
110 | ThrowStatement: node => {
|
111 | if (
|
112 | throwsErrorObject(node) &&
|
113 | isLone(node) &&
|
114 | node.parent.parent &&
|
115 | isTypechecking(node.parent.parent)
|
116 | ) {
|
117 | context.report({
|
118 | node,
|
119 | message: '`new Error()` is too unspecific for a type check. Use `new TypeError()` instead.',
|
120 | fix: fixer => fixer.replaceText(node.argument.callee, 'TypeError')
|
121 | });
|
122 | }
|
123 | }
|
124 | };
|
125 | };
|
126 |
|
127 | module.exports = {
|
128 | create,
|
129 | meta: {
|
130 | type: 'suggestion',
|
131 | docs: {
|
132 | url: getDocumentationUrl(__filename)
|
133 | },
|
134 | fixable: 'code'
|
135 | }
|
136 | };
|