-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
no-deprecated.js
139 lines (107 loc) · 4.16 KB
/
no-deprecated.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import declaredScope from 'eslint-module-utils/declaredScope';
import ExportMapBuilder from '../exportMapBuilder';
import ExportMap from '../exportMap';
import docsUrl from '../docsUrl';
function message(deprecation) {
return `Deprecated${deprecation.description ? `: ${deprecation.description}` : '.'}`;
}
function getDeprecation(metadata) {
if (!metadata || !metadata.doc) { return; }
return metadata.doc.tags.find((t) => t.title === 'deprecated');
}
module.exports = {
meta: {
type: 'suggestion',
docs: {
category: 'Helpful warnings',
description: 'Forbid imported names marked with `@deprecated` documentation tag.',
url: docsUrl('no-deprecated'),
},
schema: [],
},
create(context) {
const deprecated = new Map();
const namespaces = new Map();
function checkSpecifiers(node) {
if (node.type !== 'ImportDeclaration') { return; }
if (node.source == null) { return; } // local export, ignore
const imports = ExportMapBuilder.get(node.source.value, context);
if (imports == null) { return; }
const moduleDeprecation = imports.doc && imports.doc.tags.find((t) => t.title === 'deprecated');
if (moduleDeprecation) {
context.report({ node, message: message(moduleDeprecation) });
}
if (imports.errors.length) {
imports.reportErrors(context, node);
return;
}
node.specifiers.forEach(function (im) {
let imported; let local;
switch (im.type) {
case 'ImportNamespaceSpecifier': {
if (!imports.size) { return; }
namespaces.set(im.local.name, imports);
return;
}
case 'ImportDefaultSpecifier':
imported = 'default';
local = im.local.name;
break;
case 'ImportSpecifier':
imported = im.imported.name;
local = im.local.name;
break;
default: return; // can't handle this one
}
// unknown thing can't be deprecated
const exported = imports.get(imported);
if (exported == null) { return; }
// capture import of deep namespace
if (exported.namespace) { namespaces.set(local, exported.namespace); }
const deprecation = getDeprecation(imports.get(imported));
if (!deprecation) { return; }
context.report({ node: im, message: message(deprecation) });
deprecated.set(local, deprecation);
});
}
return {
Program: ({ body }) => body.forEach(checkSpecifiers),
Identifier(node) {
if (node.parent.type === 'MemberExpression' && node.parent.property === node) {
return; // handled by MemberExpression
}
// ignore specifier identifiers
if (node.parent.type.slice(0, 6) === 'Import') { return; }
if (!deprecated.has(node.name)) { return; }
if (declaredScope(context, node.name) !== 'module') { return; }
context.report({
node,
message: message(deprecated.get(node.name)),
});
},
MemberExpression(dereference) {
if (dereference.object.type !== 'Identifier') { return; }
if (!namespaces.has(dereference.object.name)) { return; }
if (declaredScope(context, dereference.object.name) !== 'module') { return; }
// go deep
let namespace = namespaces.get(dereference.object.name);
const namepath = [dereference.object.name];
// while property is namespace and parent is member expression, keep validating
while (namespace instanceof ExportMap && dereference.type === 'MemberExpression') {
// ignore computed parts for now
if (dereference.computed) { return; }
const metadata = namespace.get(dereference.property.name);
if (!metadata) { break; }
const deprecation = getDeprecation(metadata);
if (deprecation) {
context.report({ node: dereference.property, message: message(deprecation) });
}
// stash and pop
namepath.push(dereference.property.name);
namespace = metadata.namespace;
dereference = dereference.parent;
}
},
};
},
};