UNPKG

3.12 kBJavaScriptView Raw
1'use strict';
2const getDocumentationUrl = require('./utils/get-documentation-url');
3
4const 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
44const tcGlobalIdentifiers = new Set([
45 'isNaN',
46 'isFinite'
47]);
48
49const 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
58const throwsErrorObject = node =>
59 node.argument.type === 'NewExpression' &&
60 node.argument.callee.type === 'Identifier' &&
61 node.argument.callee.name === 'Error';
62
63const isLone = node => node.parent && node.parent.body && node.parent.body.length === 1;
64
65const 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
77const 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
106const isTypechecking = node => node.type === 'IfStatement' && isTypecheckingExpression(node.test);
107
108const 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
127module.exports = {
128 create,
129 meta: {
130 type: 'suggestion',
131 docs: {
132 url: getDocumentationUrl(__filename)
133 },
134 fixable: 'code'
135 }
136};