Skip to content

Commit 716868f

Browse files
committed
deep deprecated notices for imported namespaces (fixes #191)
1 parent 535142a commit 716868f

File tree

4 files changed

+122
-23
lines changed

4 files changed

+122
-23
lines changed

docs/rules/no-deprecated.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ function whatever(y, z) {
3333
### Worklist
3434

3535
- [x] report explicit imports on the import node
36-
- [ ] support namespaces
37-
- [ ] should bubble up through deep namespaces (#157)
36+
- [x] support namespaces
37+
- [x] should bubble up through deep namespaces (#157)
3838
- [x] report explicit imports at reference time (at the identifier) similar to namespace
3939
- [x] mark module deprecated if file JSDoc has a @deprecated tag?
40+
- [ ] don't flag redeclaration of imported, deprecated names
41+
- [ ] flag destructuring
4042

src/rules/no-deprecated.js

+57-21
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import declaredScope from '../core/declaredScope'
33

44
module.exports = function (context) {
55
const deprecated = new Map()
6+
, namespaces = new Map()
67

78
function checkSpecifiers(node) {
89
if (node.source == null) return // local export, ignore
@@ -21,30 +22,16 @@ module.exports = function (context) {
2122
return
2223
}
2324

24-
function getDeprecation(imported) {
25-
const metadata = imports.named.get(imported)
26-
if (!metadata || !metadata.doc) return
27-
28-
let deprecation
29-
if (metadata.doc.tags.some(t => t.title === 'deprecated' && (deprecation = t))) {
30-
return deprecation
31-
}
32-
}
33-
3425
node.specifiers.forEach(function (im) {
3526
let imported, local
3627
switch (im.type) {
3728

38-
// case 'ImportNamespaceSpecifier':{
39-
// const submap = new Map()
40-
// for (let name in imports.named) {
41-
// const deprecation = getDeprecation(name)
42-
// if (!deprecation) continue
43-
// submap.set(name, deprecation)
44-
// }
45-
// if (submap.size > 0) deprecated.set(im.local.name, submap)
46-
// return
47-
// }
29+
30+
case 'ImportNamespaceSpecifier':{
31+
if (!imports.named.size) return
32+
namespaces.set(im.local.name, imports.named)
33+
return
34+
}
4835

4936
case 'ImportDefaultSpecifier':
5037
imported = 'default'
@@ -62,7 +49,11 @@ module.exports = function (context) {
6249
// unknown thing can't be deprecated
6350
if (!imports.named.has(imported)) return
6451

65-
const deprecation = getDeprecation(imported)
52+
// capture named import of deep namespace
53+
const { namespace } = imports.named.get(imported)
54+
if (namespace) namespaces.set(local, namespace)
55+
56+
const deprecation = getDeprecation(imports.named.get(imported))
6657
if (!deprecation) return
6758

6859
context.report({ node: im, message: message(deprecation) })
@@ -76,6 +67,10 @@ module.exports = function (context) {
7667
'ImportDeclaration': checkSpecifiers,
7768

7869
'Identifier': function (node) {
70+
if (node.parent.type === 'MemberExpression' && node.parent.property === node) {
71+
return // handled by MemberExpression
72+
}
73+
7974
// ignore specifier identifiers
8075
if (node.parent.type.slice(0, 6) === 'Import') return
8176

@@ -87,9 +82,50 @@ module.exports = function (context) {
8782
message: message(deprecated.get(node.name)),
8883
})
8984
},
85+
86+
'MemberExpression': function (dereference) {
87+
if (dereference.object.type !== 'Identifier') return
88+
if (!namespaces.has(dereference.object.name)) return
89+
90+
if (declaredScope(context, dereference.object.name) !== 'module') return
91+
92+
// go deep
93+
var namespace = namespaces.get(dereference.object.name)
94+
var namepath = [dereference.object.name]
95+
// while property is namespace and parent is member expression, keep validating
96+
while (namespace instanceof Map &&
97+
dereference.type === 'MemberExpression') {
98+
99+
// ignore computed parts for now
100+
if (dereference.computed) return
101+
102+
const metadata = namespace.get(dereference.property.name)
103+
104+
if (!metadata) break
105+
const deprecation = getDeprecation(metadata)
106+
107+
if (deprecation) {
108+
context.report({ node: dereference.property, message: message(deprecation) })
109+
}
110+
111+
// stash and pop
112+
namepath.push(dereference.property.name)
113+
namespace = metadata.namespace
114+
dereference = dereference.parent
115+
}
116+
},
90117
}
91118
}
92119

93120
function message(deprecation) {
94121
return 'Deprecated' + (deprecation.description ? ': ' + deprecation.description : '.')
95122
}
123+
124+
function getDeprecation(metadata) {
125+
if (!metadata || !metadata.doc) return
126+
127+
let deprecation
128+
if (metadata.doc.tags.some(t => t.title === 'deprecated' && (deprecation = t))) {
129+
return deprecation
130+
}
131+
}

tests/files/deep-deprecated.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import * as deepDep from './deprecated'
2+
export { deepDep }

tests/src/rules/no-deprecated.js

+59
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ ruleTester.run('no-deprecated', rule, {
1515

1616
// naked namespace is fine
1717
test({ code: "import * as depd from './deprecated'" }),
18+
test({ code: "import * as depd from './deprecated'; console.log(depd.fine())" }),
19+
test({ code: "import { deepDep } from './deep-deprecated'" }),
20+
test({ code: "import { deepDep } from './deep-deprecated'; console.log(deepDep.fine())" }),
21+
22+
// redefined
23+
test({
24+
code: "import { deepDep } from './deep-deprecated'; function x(deepDep) { console.log(deepDep.MY_TERRIBLE_ACTION) }",
25+
}),
1826
],
1927
invalid: [
2028

@@ -57,6 +65,23 @@ ruleTester.run('no-deprecated', rule, {
5765
],
5866
}),
5967

68+
// don't flag other members
69+
test({
70+
code: "import { MY_TERRIBLE_ACTION } from './deprecated'; console.log(someOther.MY_TERRIBLE_ACTION)",
71+
errors: [
72+
{ type: 'ImportSpecifier', message: 'Deprecated: please stop sending/handling this action type.' },
73+
],
74+
}),
75+
76+
// flag it even with members
77+
test({
78+
code: "import { MY_TERRIBLE_ACTION } from './deprecated'; console.log(MY_TERRIBLE_ACTION.whatever())",
79+
errors: [
80+
{ type: 'ImportSpecifier', message: 'Deprecated: please stop sending/handling this action type.' },
81+
{ type: 'Identifier', message: 'Deprecated: please stop sending/handling this action type.' },
82+
],
83+
}),
84+
6085
// works for function calls too
6186
test({
6287
code: "import { MY_TERRIBLE_ACTION } from './deprecated'; console.log(MY_TERRIBLE_ACTION(this, is, the, worst))",
@@ -73,5 +98,39 @@ ruleTester.run('no-deprecated', rule, {
7398
{ type: 'ImportDeclaration', message: 'Deprecated: this module is the worst.' },
7499
],
75100
}),
101+
102+
// don't flag as part of other member expressions
103+
test({
104+
code: "import Thing from './deprecated-file'; console.log(other.Thing)",
105+
errors: [
106+
{ type: 'ImportDeclaration', message: 'Deprecated: this module is the worst.' },
107+
],
108+
}),
109+
110+
// namespace following
111+
test({
112+
code: "import * as depd from './deprecated'; console.log(depd.MY_TERRIBLE_ACTION)",
113+
errors: [
114+
{ type: 'Identifier', message: 'Deprecated: please stop sending/handling this action type.' },
115+
],
116+
}),
117+
test({
118+
code: "import * as deep from './deep-deprecated'; console.log(deep.deepDep.MY_TERRIBLE_ACTION)",
119+
errors: [
120+
{ type: 'Identifier', message: 'Deprecated: please stop sending/handling this action type.' },
121+
],
122+
}),
123+
test({
124+
code: "import { deepDep } from './deep-deprecated'; console.log(deepDep.MY_TERRIBLE_ACTION)",
125+
errors: [
126+
{ type: 'Identifier', message: 'Deprecated: please stop sending/handling this action type.' },
127+
],
128+
}),
129+
test({
130+
code: "import { deepDep } from './deep-deprecated'; function x(deepNDep) { console.log(deepDep.MY_TERRIBLE_ACTION) }",
131+
errors: [
132+
{ type: 'Identifier', message: 'Deprecated: please stop sending/handling this action type.' },
133+
],
134+
}),
76135
],
77136
})

0 commit comments

Comments
 (0)