Skip to content

Commit

Permalink
deep deprecated notices for imported namespaces (fixes #191)
Browse files Browse the repository at this point in the history
  • Loading branch information
benmosher committed Feb 26, 2016
1 parent 535142a commit 716868f
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 23 deletions.
6 changes: 4 additions & 2 deletions docs/rules/no-deprecated.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ function whatever(y, z) {
### Worklist

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

78 changes: 57 additions & 21 deletions src/rules/no-deprecated.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import declaredScope from '../core/declaredScope'

module.exports = function (context) {
const deprecated = new Map()
, namespaces = new Map()

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

function getDeprecation(imported) {
const metadata = imports.named.get(imported)
if (!metadata || !metadata.doc) return

let deprecation
if (metadata.doc.tags.some(t => t.title === 'deprecated' && (deprecation = t))) {
return deprecation
}
}

node.specifiers.forEach(function (im) {
let imported, local
switch (im.type) {

// case 'ImportNamespaceSpecifier':{
// const submap = new Map()
// for (let name in imports.named) {
// const deprecation = getDeprecation(name)
// if (!deprecation) continue
// submap.set(name, deprecation)
// }
// if (submap.size > 0) deprecated.set(im.local.name, submap)
// return
// }

case 'ImportNamespaceSpecifier':{
if (!imports.named.size) return
namespaces.set(im.local.name, imports.named)
return
}

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

const deprecation = getDeprecation(imported)
// capture named import of deep namespace
const { namespace } = imports.named.get(imported)
if (namespace) namespaces.set(local, namespace)

const deprecation = getDeprecation(imports.named.get(imported))
if (!deprecation) return

context.report({ node: im, message: message(deprecation) })
Expand All @@ -76,6 +67,10 @@ module.exports = function (context) {
'ImportDeclaration': 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

Expand All @@ -87,9 +82,50 @@ module.exports = function (context) {
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 Map &&
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
}
},
}
}

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
}
}
2 changes: 2 additions & 0 deletions tests/files/deep-deprecated.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import * as deepDep from './deprecated'
export { deepDep }
59 changes: 59 additions & 0 deletions tests/src/rules/no-deprecated.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ ruleTester.run('no-deprecated', rule, {

// naked namespace is fine
test({ code: "import * as depd from './deprecated'" }),
test({ code: "import * as depd from './deprecated'; console.log(depd.fine())" }),
test({ code: "import { deepDep } from './deep-deprecated'" }),
test({ code: "import { deepDep } from './deep-deprecated'; console.log(deepDep.fine())" }),

// redefined
test({
code: "import { deepDep } from './deep-deprecated'; function x(deepDep) { console.log(deepDep.MY_TERRIBLE_ACTION) }",
}),
],
invalid: [

Expand Down Expand Up @@ -57,6 +65,23 @@ ruleTester.run('no-deprecated', rule, {
],
}),

// don't flag other members
test({
code: "import { MY_TERRIBLE_ACTION } from './deprecated'; console.log(someOther.MY_TERRIBLE_ACTION)",
errors: [
{ type: 'ImportSpecifier', message: 'Deprecated: please stop sending/handling this action type.' },
],
}),

// flag it even with members
test({
code: "import { MY_TERRIBLE_ACTION } from './deprecated'; console.log(MY_TERRIBLE_ACTION.whatever())",
errors: [
{ type: 'ImportSpecifier', message: 'Deprecated: please stop sending/handling this action type.' },
{ type: 'Identifier', message: 'Deprecated: please stop sending/handling this action type.' },
],
}),

// works for function calls too
test({
code: "import { MY_TERRIBLE_ACTION } from './deprecated'; console.log(MY_TERRIBLE_ACTION(this, is, the, worst))",
Expand All @@ -73,5 +98,39 @@ ruleTester.run('no-deprecated', rule, {
{ type: 'ImportDeclaration', message: 'Deprecated: this module is the worst.' },
],
}),

// don't flag as part of other member expressions
test({
code: "import Thing from './deprecated-file'; console.log(other.Thing)",
errors: [
{ type: 'ImportDeclaration', message: 'Deprecated: this module is the worst.' },
],
}),

// namespace following
test({
code: "import * as depd from './deprecated'; console.log(depd.MY_TERRIBLE_ACTION)",
errors: [
{ type: 'Identifier', message: 'Deprecated: please stop sending/handling this action type.' },
],
}),
test({
code: "import * as deep from './deep-deprecated'; console.log(deep.deepDep.MY_TERRIBLE_ACTION)",
errors: [
{ type: 'Identifier', message: 'Deprecated: please stop sending/handling this action type.' },
],
}),
test({
code: "import { deepDep } from './deep-deprecated'; console.log(deepDep.MY_TERRIBLE_ACTION)",
errors: [
{ type: 'Identifier', message: 'Deprecated: please stop sending/handling this action type.' },
],
}),
test({
code: "import { deepDep } from './deep-deprecated'; function x(deepNDep) { console.log(deepDep.MY_TERRIBLE_ACTION) }",
errors: [
{ type: 'Identifier', message: 'Deprecated: please stop sending/handling this action type.' },
],
}),
],
})

0 comments on commit 716868f

Please sign in to comment.