Skip to content

Commit

Permalink
Add TomDoc support to no-deprecated rule
Browse files Browse the repository at this point in the history
  • Loading branch information
josh committed May 26, 2016
1 parent e6e7551 commit 23b9d12
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
## [Unreleased]
### Added
- Added support for multiple webpack configs ([#181], thanks [@GreenGremlin])
- Added support TomDoc comments to `no-deprecated` ([#321], thanks [@josh])

## [Unreleased]
### Fixed
Expand Down
27 changes: 25 additions & 2 deletions docs/rules/no-deprecated.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
**NOTE**: this rule is currently a work in progress. There may be "breaking" changes: most likely, additional cases that are flagged.

Reports use of a deprecated name, as indicated by a JSDoc block with a `@deprecated`
tag, i.e.
tag or TomDoc `Deprecated: ` comment.

using a JSDoc `@deprecated` tag:

```js
// @file: ./answer.js
Expand All @@ -30,6 +32,28 @@ function whatever(y, z) {
}
```

or using the TomDoc equivalent:

```js
// Deprecated: This is what you get when you trust a mouse talk show, need to
// restart the experiment.
//
// Returns a Number nonsense
export function multiply(six, nine) {
return 42
}
```

Only JSDoc is enabled by default. Other documentation styles can be enabled with
the `import/docstyle` setting.


```yaml
# .eslintrc.yml
settings:
import/docstyle: ['jsdoc', 'tomdoc']
```

### Worklist

- [x] report explicit imports on the import node
Expand All @@ -39,4 +63,3 @@ function whatever(y, z) {
- [x] mark module deprecated if file JSDoc has a @deprecated tag?
- [ ] don't flag redeclaration of imported, deprecated names
- [ ] flag destructuring

84 changes: 70 additions & 14 deletions src/core/getExports.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ export default class ExportMap {
return m // can't continue
}

const docstyle = (context.settings && context.settings['import/docstyle']) || ['jsdoc']
const docStyleParsers = {}
docstyle.forEach(style => {
docStyleParsers[style] = availableDocStyleParsers[style]
})

// attempt to collect module doc
ast.comments.some(c => {
if (c.type !== 'Block') return false
Expand Down Expand Up @@ -143,7 +149,7 @@ export default class ExportMap {
ast.body.forEach(function (n) {

if (n.type === 'ExportDefaultDeclaration') {
const exportMeta = captureDoc(n)
const exportMeta = captureDoc(docStyleParsers, n)
if (n.declaration.type === 'Identifier') {
addNamespace(exportMeta, n.declaration)
}
Expand Down Expand Up @@ -174,11 +180,12 @@ export default class ExportMap {
case 'FunctionDeclaration':
case 'ClassDeclaration':
case 'TypeAlias': // flowtype with babel-eslint parser
m.namespace.set(n.declaration.id.name, captureDoc(n))
m.namespace.set(n.declaration.id.name, captureDoc(docStyleParsers, n))
break
case 'VariableDeclaration':
n.declaration.declarations.forEach((d) =>
recursivePatternCapture(d.id, id => m.namespace.set(id.name, captureDoc(d, n))))
recursivePatternCapture(d.id, id =>
m.namespace.set(id.name, captureDoc(docStyleParsers, d, n))))
break
}
}
Expand Down Expand Up @@ -348,33 +355,82 @@ export default class ExportMap {
}

/**
* parse JSDoc from the first node that has leading comments
* parse docs from the first node that has leading comments
* @param {...[type]} nodes [description]
* @return {{doc: object}}
*/
function captureDoc(...nodes) {
function captureDoc(docStyleParsers, ...nodes) {
const metadata = {}

// 'some' short-circuits on first 'true'
nodes.some(n => {
if (!n.leadingComments) return false

// capture XSDoc
n.leadingComments.forEach(comment => {
// skip non-block comments
if (comment.value.slice(0, 4) !== '*\n *') return
try {
metadata.doc = doctrine.parse(comment.value, { unwrap: true })
} catch (err) {
/* don't care, for now? maybe add to `errors?` */
for (let name in docStyleParsers) {
const doc = docStyleParsers[name](n.leadingComments)
if (doc) {
metadata.doc = doc
}
})
}

return true
})

return metadata
}

const availableDocStyleParsers = {
jsdoc: captureJsDoc,
tomdoc: captureTomDoc,
}

/**
* parse JSDoc from leading comments
* @param {...[type]} comments [description]
* @return {{doc: object}}
*/
function captureJsDoc(comments) {
let doc

// capture XSDoc
comments.forEach(comment => {
// skip non-block comments
if (comment.value.slice(0, 4) !== '*\n *') return
try {
doc = doctrine.parse(comment.value, { unwrap: true })
} catch (err) {
/* don't care, for now? maybe add to `errors?` */
}
})

return doc
}

/**
* parse TomDoc section from comments
*/
function captureTomDoc(comments) {
// collect lines up to first paragraph break
const lines = []
for (let i = 0; i < comments.length; i++) {
const comment = comments[i]
if (comment.value.match(/^\s*$/)) break
lines.push(comment.value.trim())
}

// return doctrine-like object
const statusMatch = lines.join(' ').match(/^(Public|Internal|Deprecated):\s*(.+)/)
if (statusMatch) {
return {
description: statusMatch[2],
tags: [{
title: statusMatch[1].toLowerCase(),
description: statusMatch[2],
}],
}
}
}

/**
* Traverse a pattern/identifier node, calling 'callback'
* for each leaf identifier.
Expand Down
22 changes: 22 additions & 0 deletions tests/files/tomdoc-deprecated.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Deprecated: This function is terrible.
//
// With another line comment in description.
export function fn() { return null }

// Deprecated: this is awful,
// use NotAsBadClass.
//
// Some other description text.
export default class TerribleClass {

}

// Deprecated: Please stop sending/handling this action type.
export const MY_TERRIBLE_ACTION = "ugh"

// Public: This one is fine.
//
// Returns a String "great!"
export function fine() { return "great!" }

export function _undocumented() { return "sneaky!" }
38 changes: 38 additions & 0 deletions tests/src/rules/no-deprecated.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ ruleTester.run('no-deprecated', rule, {
test({ code: "import { fine } from './deprecated'" }),
test({ code: "import { _undocumented } from './deprecated'" }),

test({
code: "import { fn } from './deprecated'",
settings: { 'import/docstyle': ['tomdoc'] }
}),

test({
code: "import { fine } from './tomdoc-deprecated'",
settings: { 'import/docstyle': ['tomdoc'] }
}),
test({
code: "import { _undocumented } from './tomdoc-deprecated'",
settings: { 'import/docstyle': ['tomdoc'] }
}),

// naked namespace is fine
test({ code: "import * as depd from './deprecated'" }),
test({ code: "import * as depd from './deprecated'; console.log(depd.fine())" }),
Expand Down Expand Up @@ -44,6 +58,30 @@ ruleTester.run('no-deprecated', rule, {
errors: ['Deprecated: please stop sending/handling this action type.'],
}),

test({
code: "import { fn } from './deprecated'",
settings: { 'import/docstyle': ['jsdoc', 'tomdoc'] },
errors: ["Deprecated: please use 'x' instead."],
}),

test({
code: "import { fn } from './tomdoc-deprecated'",
settings: { 'import/docstyle': ['tomdoc'] },
errors: ["Deprecated: This function is terrible."],
}),

test({
code: "import TerribleClass from './tomdoc-deprecated'",
settings: { 'import/docstyle': ['tomdoc'] },
errors: ['Deprecated: this is awful, use NotAsBadClass.'],
}),

test({
code: "import { MY_TERRIBLE_ACTION } from './tomdoc-deprecated'",
settings: { 'import/docstyle': ['tomdoc'] },
errors: ['Deprecated: Please stop sending/handling this action type.'],
}),

// ignore redeclares
test({
code: "import { MY_TERRIBLE_ACTION } from './deprecated'; function shadow(MY_TERRIBLE_ACTION) { console.log(MY_TERRIBLE_ACTION); }",
Expand Down

0 comments on commit 23b9d12

Please sign in to comment.