UNPKG

3.03 kBJavaScriptView Raw
1'use strict';
2const {findVariable} = require('eslint-utils');
3const avoidCapture = require('./utils/avoid-capture');
4const getDocumentationUrl = require('./utils/get-documentation-url');
5const renameVariable = require('./utils/rename-variable');
6const methodSelector = require('./utils/method-selector');
7
8const ERROR_MESSAGE_ID = 'error';
9
10const promiseMethodSelector = (method, argumentsLength, argumentIndex) => [
11 methodSelector({
12 name: method,
13 length: argumentsLength
14 }),
15 `:matches(${
16 [
17 'FunctionExpression',
18 'ArrowFunctionExpression'
19 ].map(type => `[arguments.${argumentIndex}.type="${type}"]`).join(', ')
20 })`,
21 `[arguments.${argumentIndex}.params.length=1]`,
22 `[arguments.${argumentIndex}.params.0.type="Identifier"]`
23].join('');
24
25// Matches `promise.catch([FunctionExpression | ArrowFunctionExpression])`
26const promiseCatchSelector = promiseMethodSelector('catch', 1, 0);
27
28// Matches `promise.then(any, [FunctionExpression | ArrowFunctionExpression])`
29const promiseThenSelector = promiseMethodSelector('then', 2, 1);
30
31const catchSelector = [
32 'CatchClause',
33 '>',
34 'Identifier.param'
35].join('');
36
37const create = context => {
38 const {ecmaVersion} = context.parserOptions;
39 const sourceCode = context.getSourceCode();
40
41 const options = {
42 name: 'error',
43 ignore: [],
44 ...context.options[0]
45 };
46 const {name: expectedName} = options;
47 const ignore = options.ignore.map(
48 pattern => pattern instanceof RegExp ? pattern : new RegExp(pattern, 'u')
49 );
50 const isNameAllowed = name =>
51 name === expectedName ||
52 ignore.some(regexp => regexp.test(name)) ||
53 name.endsWith(expectedName) ||
54 name.endsWith(expectedName.charAt(0).toUpperCase() + expectedName.slice(1));
55
56 function check(node) {
57 const originalName = node.name;
58
59 if (
60 isNameAllowed(originalName) ||
61 isNameAllowed(originalName.replace(/_+$/g, ''))
62 ) {
63 return;
64 }
65
66 const scope = context.getScope();
67 const variable = findVariable(scope, node);
68
69 if (originalName === '_' && variable.references.length === 0) {
70 return;
71 }
72
73 const scopes = [
74 variable.scope,
75 ...variable.references.map(({from}) => from)
76 ];
77 const fixedName = avoidCapture(expectedName, scopes, ecmaVersion);
78
79 context.report({
80 node,
81 messageId: ERROR_MESSAGE_ID,
82 data: {
83 originalName,
84 fixedName
85 },
86 fix: fixer => renameVariable(variable, fixedName, fixer, sourceCode)
87 });
88 }
89
90 return {
91 [promiseCatchSelector]: node => {
92 check(node.arguments[0].params[0]);
93 },
94 [promiseThenSelector]: node => {
95 check(node.arguments[1].params[0]);
96 },
97 [catchSelector]: node => {
98 check(node);
99 }
100 };
101};
102
103const schema = [
104 {
105 type: 'object',
106 properties: {
107 name: {
108 type: 'string'
109 },
110 ignore: {
111 type: 'array',
112 uniqueItems: true
113 }
114 }
115 }
116];
117
118module.exports = {
119 create,
120 meta: {
121 type: 'suggestion',
122 docs: {
123 url: getDocumentationUrl(__filename)
124 },
125 fixable: 'code',
126 schema,
127 messages: {
128 [ERROR_MESSAGE_ID]: 'The catch parameter `{{originalName}}` should be named `{{fixedName}}`.'
129 }
130 }
131};