-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
no-deprecated.js
142 lines (108 loc) · 3.97 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
140
141
142
import declaredScope from 'eslint-module-utils/declaredScope'
import Exports from '../ExportMap'
import docsUrl from '../docsUrl'
function message(deprecation) {
return 'Deprecated' + (deprecation.description ? ': ' + deprecation.description : '.')
}
function getDeprecation(metadata) {
if (!metadata || !metadata.doc) return
let deprecation
if (metadata.doc.tags.some(t => t.title === 'deprecated' && (deprecation = t))) {
return deprecation
}
}
module.exports = {
meta: {
type: 'suggestion',
docs: {
url: docsUrl('no-deprecated'),
},
},
create: function (context) {
const deprecated = new Map()
, namespaces = new Map()
function checkSpecifiers(node) {
if (node.type !== 'ImportDeclaration') return
if (node.source == null) return // local export, ignore
const imports = Exports.get(node.source.value, context)
if (imports == null) return
let moduleDeprecation
if (imports.doc &&
imports.doc.tags.some(t => t.title === 'deprecated' && (moduleDeprecation = t))) {
context.report({ node, message: message(moduleDeprecation) })
}
if (imports.errors.length) {
imports.reportErrors(context, node)
return
}
node.specifiers.forEach(function (im) {
let imported, 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': function (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': function (dereference) {
if (dereference.object.type !== 'Identifier') return
if (!namespaces.has(dereference.object.name)) return
if (declaredScope(context, dereference.object.name) !== 'module') return
// go deep
var namespace = namespaces.get(dereference.object.name)
var namepath = [dereference.object.name]
// while property is namespace and parent is member expression, keep validating
while (namespace instanceof Exports &&
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
}
},
}
},
}