Skip to content

Commit 56a52e9

Browse files
authored
Lint - add function-deprecation (#780)
* Lint - add function-deprecation (rebase) * Fix missing function-deprecation_warn
1 parent f0c628e commit 56a52e9

File tree

7 files changed

+536
-16
lines changed

7 files changed

+536
-16
lines changed

eslint-rules/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ module.exports = {
55
'component-deprecation': require('./lib/rules/component-deprecation'),
66
'assets-deprecation': require('./lib/rules/assets-deprecation'),
77
'typography-deprecation': require('./lib/rules/typography-deprecation'),
8+
'function-deprecation': require('./lib/rules/function-deprecation'),
89
// for duplicate rules usage
910
'component-deprecation_warn': require('./lib/rules/component-deprecation'),
1011
'assets-deprecation_warn': require('./lib/rules/assets-deprecation'),
1112
'typography-deprecation_warn': require('./lib/rules/typography-deprecation'),
13+
'function-deprecation_warn': require('./lib/rules/function-deprecation'),
1214
'component-deprecation_error': require('./lib/rules/component-deprecation'),
1315
'assets-deprecation_error': require('./lib/rules/assets-deprecation'),
1416
'typography-deprecation_error': require('./lib/rules/typography-deprecation'),
17+
'function-deprecation_error': require('./lib/rules/function-deprecation'),
1518
},
1619
};

eslint-rules/lib/rules/component-deprecation.js

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ module.exports = {
1919
recommended: true,
2020
},
2121
messages: {
22-
uiLib: 'This component is deprecated or containes deprecated props.',
22+
uiLib: 'This component is deprecated or contains deprecated props.',
2323
},
2424
fixable: 'code',
2525
schema: [MAP_SCHEMA],
@@ -48,7 +48,7 @@ module.exports = {
4848
return fixer.replaceText(node.name, fix);
4949
case FIX_TYPES.COMPONENT_NAME:
5050
if (node.type === 'ImportDeclaration') {
51-
const index = getSpecifierIndex(node, options.name);
51+
const index = utils.getSpecifierIndex(node, options.name);
5252
return fixer.replaceText(node.specifiers[index], fix);
5353
}
5454
return fixer.replaceText(node.name, fix);
@@ -63,19 +63,6 @@ module.exports = {
6363
}
6464
}
6565

66-
function getSpecifierIndex(node, name) {
67-
let matchIndex;
68-
if (node && node.specifiers) {
69-
_.forEach(node.specifiers, (s, index) => {
70-
const x = _.get(s, 'imported.name');
71-
if (x === name) {
72-
matchIndex = index;
73-
}
74-
});
75-
}
76-
return matchIndex;
77-
}
78-
7966
function checkPropDeprecation(node, propName, deprecatedPropList, componentName) {
8067
const deprecatedProp = _.find(deprecatedPropList, {prop: propName});
8168
if (deprecatedProp) {
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
const _ = require("lodash");
2+
const utils = require("../utils");
3+
4+
const MAP_SCHEMA = {
5+
type: "object",
6+
additionalProperties: true,
7+
};
8+
9+
const FIX_TYPES = {
10+
PROP_NAME: "propName",
11+
FUNCTION_NAME: "functionName",
12+
};
13+
14+
module.exports = {
15+
meta: {
16+
docs: {
17+
description: "function or some of the props sent to it are deprecated",
18+
category: "Best Practices",
19+
recommended: true,
20+
},
21+
messages: {
22+
uiLib: "This function is deprecated or contains deprecated props.",
23+
},
24+
fixable: "code",
25+
schema: [MAP_SCHEMA],
26+
},
27+
create(context) {
28+
function reportDeprecatedFunction(node, options) {
29+
try {
30+
const { dueDate } = context.options[0];
31+
const dueDateNotice = dueDate
32+
? ` Please fix this issue by ${dueDate}!`
33+
: "";
34+
const msg =
35+
options.prop === undefined
36+
? `The '${options.name}' function is deprecated. ${options.message}${dueDateNotice}`
37+
: `The '${options.name}' function's prop '${options.prop}' is deprecated. ${options.message}${dueDateNotice}`;
38+
39+
context.report({
40+
node,
41+
message: `${msg}`,
42+
fix(fixer) {
43+
if (options.fix) {
44+
const type = Object.keys(options.fix)[0];
45+
const fix = Object.values(options.fix)[0];
46+
let fixed;
47+
// console.warn('fix');
48+
switch (type) {
49+
case FIX_TYPES.PROP_NAME:
50+
// Fix for prop name change only (when prop's value and type does not change)
51+
// console.warn('fix prop');
52+
// console.warn('from', node.arguments[options.argumentIndex]);
53+
const prop = _.find(node.arguments[options.argumentIndex].properties, prop => prop.key.name === options.prop);
54+
const propIndex = node.arguments[options.argumentIndex].properties.indexOf(prop);
55+
fixed = fixer.replaceText(node.arguments[options.argumentIndex].properties[propIndex], fix)
56+
// console.warn('to', fixed);
57+
return fixed;
58+
case FIX_TYPES.FUNCTION_NAME:
59+
if (node.type === "ImportDeclaration") {
60+
// console.warn('fix function import');
61+
const index = utils.getSpecifierIndex(node, options.name);
62+
// console.warn('from', node.specifiers[index]);
63+
fixed = fixer.replaceText(node.specifiers[index], fix);
64+
// console.warn('to', fixed);
65+
return fixed;
66+
}
67+
68+
// console.warn('fix function not import');
69+
// console.warn('from', node.callee.name);
70+
fixed = fixer.replaceText(node.callee, fix)
71+
// console.warn('to', fixed);
72+
return fixed;
73+
default:
74+
break;
75+
}
76+
}
77+
},
78+
});
79+
} catch (err) {
80+
console.log("Found error in: ", err, context.getFilename());
81+
}
82+
}
83+
84+
const { deprecations, source } = context.options[0];
85+
const relevantDeprecationsData = [];
86+
let everythingIsImported = false;
87+
88+
function getDeprecation(value) {
89+
if (value && value.name) {
90+
const name = value.name;
91+
return _.find(
92+
deprecations,
93+
(deprecation) => deprecation.function === name
94+
);
95+
}
96+
}
97+
98+
function searchForPossibleDeprecation(node) {
99+
const importSource = node.source.value;
100+
if (source === importSource) {
101+
const specifiers = node.specifiers;
102+
if (specifiers) {
103+
_.map(specifiers, (specifier) => {
104+
const deprecation = getDeprecation(specifier.imported);
105+
if (deprecation) {
106+
let type = FIX_TYPES.PROP_NAME;
107+
if (!deprecation.arguments) {
108+
type = FIX_TYPES.FUNCTION_NAME;
109+
reportDeprecatedFunction(node, {
110+
name: deprecation.function,
111+
message: deprecation.message,
112+
fix: deprecation.fix,
113+
});
114+
}
115+
116+
relevantDeprecationsData.push({
117+
localFunctionName: specifier.local.name,
118+
type,
119+
deprecation,
120+
});
121+
}
122+
});
123+
}
124+
125+
if (relevantDeprecationsData.length === 0) { // someone is importing everything (*)
126+
everythingIsImported = true;
127+
_.map(deprecations, deprecation => {
128+
relevantDeprecationsData.push({
129+
localFunctionName: deprecation.function,
130+
type: deprecation.arguments ? FIX_TYPES.PROP_NAME : FIX_TYPES.FUNCTION_NAME,
131+
deprecation,
132+
});
133+
});
134+
}
135+
}
136+
}
137+
138+
function findRelevantDeprecation(functionName) {
139+
return _.find(
140+
relevantDeprecationsData,
141+
(relevantDeprecationData) =>
142+
relevantDeprecationData.localFunctionName === functionName
143+
);
144+
}
145+
146+
function getArgumentsSent(node) {
147+
const argumentsSent = [];
148+
_.map(node.arguments, argument => {
149+
const propsSentToArgument = [];
150+
if (argument.properties) {
151+
_.map(argument.properties, prop => {
152+
if (prop.key && prop.key.name) {
153+
propsSentToArgument.push(prop.key.name);
154+
}
155+
});
156+
}
157+
158+
argumentsSent.push(propsSentToArgument);
159+
});
160+
161+
return argumentsSent;
162+
}
163+
164+
function getFunctionName(node) {
165+
const propName = everythingIsImported ? 'callee.property.name' : 'callee.name';
166+
return _.get(node, propName);
167+
}
168+
169+
function testCallExpression(node) {
170+
const functionName = getFunctionName(node);
171+
if (functionName) {
172+
const relevantDeprecation = findRelevantDeprecation(functionName);
173+
if (relevantDeprecation) {
174+
if (relevantDeprecation.type === FIX_TYPES.PROP_NAME) {
175+
const argumentsSent = getArgumentsSent(node);
176+
_.map(relevantDeprecation.deprecation.arguments, (argument, index) => {
177+
if (argument.props && argument.props.length > 0 && argumentsSent.length >= index) {
178+
const deprecationProps = argument.props;
179+
const sentProps = argumentsSent[index];
180+
_.map(sentProps, sentProp => {
181+
const deprecationProp = _.find(deprecationProps, deprecationProp => deprecationProp.prop === sentProp);
182+
if (deprecationProp) {
183+
reportDeprecatedFunction(node, {
184+
name: functionName,
185+
message: deprecationProp.message,
186+
argumentIndex: index,
187+
prop: deprecationProp.prop,
188+
fix: deprecationProp.fix,
189+
});
190+
}
191+
});
192+
}
193+
});
194+
} else {
195+
reportDeprecatedFunction(node, {
196+
name: relevantDeprecation.deprecation.function,
197+
message: relevantDeprecation.deprecation.message,
198+
fix: relevantDeprecation.deprecation.fix,
199+
});
200+
}
201+
}
202+
}
203+
}
204+
205+
return {
206+
ImportDeclaration: (node) => searchForPossibleDeprecation(node),
207+
CallExpression: (node) => relevantDeprecationsData.length > 0 && testCallExpression(node),
208+
209+
// MemberExpression: node => localImportSpecifier && testMemberDeprecation(node),
210+
// JSXAttribute: node => testJSXAttribute(node),
211+
// JSXOpeningElement: node => testJSXOpeningElement(node),
212+
// ObjectExpression: node => testObjectExpression(node),
213+
// VariableDeclarator: node => testVariableDeclarator(node),
214+
// Property: node => testProperty(node),
215+
// JSXSpreadAttribute: node => testJSXSpreadAttribute(node)
216+
};
217+
},
218+
};

eslint-rules/lib/utils.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,19 @@ function getLocalImportSpecifier(node, source, defaultImportName) {
7979
return localImportSpecifier;
8080
}
8181

82+
function getSpecifierIndex(node, name) {
83+
let matchIndex;
84+
if (node && node.specifiers) {
85+
_.forEach(node.specifiers, (s, index) => {
86+
const x = _.get(s, 'imported.name');
87+
if (x === name) {
88+
matchIndex = index;
89+
}
90+
});
91+
}
92+
return matchIndex;
93+
}
94+
8295
module.exports = {
8396
isPropFont,
8497
findAndReportHardCodedValues,
@@ -87,4 +100,5 @@ module.exports = {
87100
isLiteral,
88101
findValueNodeOfIdentifier,
89102
getLocalImportSpecifier,
103+
getSpecifierIndex,
90104
};

eslint-rules/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "eslint-plugin-uilib",
3-
"version": "1.0.25",
3+
"version": "1.0.26",
44
"description": "uilib set of eslint rules",
55
"keywords": [
66
"eslint",
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[
2+
{
3+
"function": "deprecatedFunction",
4+
"source": "our-source",
5+
"message": "Please use the 'validFunction' function instead (fix is available).",
6+
"fix": {"functionName": "validFunction"}
7+
},
8+
{
9+
"function": "validFunction",
10+
"source": "our-source",
11+
"message": "",
12+
"arguments": [
13+
{},
14+
{
15+
"props": [
16+
{
17+
"prop": "deprecatedProp",
18+
"message": "Please use the 'validProp' prop instead (fix is available).",
19+
"fix": {"propName": "validProp"}
20+
}
21+
]
22+
}
23+
]
24+
}
25+
]

0 commit comments

Comments
 (0)