Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TomDoc support to no-deprecated rule #321

Merged
merged 1 commit into from
Jun 1, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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