Skip to content

Commit d7e2c95

Browse files
committed
Add rule to require Markdown definitions be placed at end of desc
1 parent 36829f7 commit d7e2c95

9 files changed

Lines changed: 870 additions & 0 deletions

File tree

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Final Definition
2+
3+
> [ESLint rule][eslint-rules] to require Markdown definitions in JSDoc descriptions be placed at the end of the description.
4+
5+
<section class="intro">
6+
7+
</section>
8+
9+
<!-- /.intro -->
10+
11+
<section class="usage">
12+
13+
## Usage
14+
15+
```javascript
16+
var rule = require( '@stdlib/_tools/eslint/rules/jsdoc-final-definition' );
17+
```
18+
19+
#### rule
20+
21+
[ESLint rule][eslint-rules] to require Markdown definitions in JSDoc descriptions be placed at the end of the description.
22+
23+
**Bad**:
24+
25+
<!-- eslint-disable stdlib/jsdoc-final-definition, stdlib/jsdoc-markdown-remark -->
26+
27+
```javascript
28+
/**
29+
* Beep [boop][boop].
30+
*
31+
* [boop]: http://example.com
32+
*
33+
* Foo bar.
34+
*
35+
* @returns {string} a value
36+
*
37+
* @example
38+
* var str = beep();
39+
* // returns 'boop'
40+
*/
41+
function beep() {
42+
return 'boop';
43+
}
44+
```
45+
46+
**Good**:
47+
48+
```javascript
49+
/**
50+
* Beep [boop][boop].
51+
*
52+
* Foo bar.
53+
*
54+
* [boop]: http://example.com
55+
*
56+
* @returns {string} a value
57+
*
58+
* @example
59+
* var str = beep();
60+
* // returns 'boop'
61+
*/
62+
function beep() {
63+
return 'boop';
64+
}
65+
```
66+
67+
</section>
68+
69+
<!-- /.usage -->
70+
71+
<section class="examples">
72+
73+
## Examples
74+
75+
```javascript
76+
var Linter = require( 'eslint' ).Linter;
77+
var rule = require( '@stdlib/_tools/eslint/rules/jsdoc-final-definition' );
78+
79+
var linter = new Linter();
80+
var result;
81+
var code;
82+
83+
// Generate our source code:
84+
code = [
85+
'/**',
86+
'* Beep [boop][boop].',
87+
'*',
88+
'* [boop]: http://example.com',
89+
'*',
90+
'* Foo bar.',
91+
'*',
92+
'*',
93+
'* @param {string} str - input value',
94+
'* @returns {string} output value',
95+
'*',
96+
'* @example',
97+
'* var out = beep( "boop" );',
98+
'* // returns "beepboop"',
99+
'*/',
100+
'function beep( str ) {',
101+
'\treturn "beep" + str;',
102+
'}'
103+
].join( '\n' );
104+
105+
// Register the ESLint rule:
106+
linter.defineRule( 'jsdoc-final-definition', rule );
107+
108+
// Lint the code:
109+
result = linter.verify( code, {
110+
'rules': {
111+
'jsdoc-final-definition': 'error'
112+
}
113+
});
114+
console.log( result );
115+
/* =>
116+
[
117+
{
118+
'ruleId': 'jsdoc-final-definition',
119+
'severity': 2,
120+
'message': 'Move definitions to the end of the description (after the node at line `5`)',
121+
'line': 4,
122+
'column': 3,
123+
'nodeType': null,
124+
'source': '* [boop]: http://example.com',
125+
'endLine': 14,
126+
'endColumn': 3
127+
}
128+
]
129+
*/
130+
```
131+
132+
</section>
133+
134+
<!-- /.examples -->
135+
136+
<section class="links">
137+
138+
[eslint-rules]: https://eslint.org/docs/developer-guide/working-with-rules
139+
140+
</section>
141+
142+
<!-- /.links -->
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use strict';
2+
3+
var Linter = require( 'eslint' ).Linter;
4+
var rule = require( './../lib' );
5+
6+
var linter = new Linter();
7+
var result;
8+
var code;
9+
10+
// Generate our source code:
11+
code = [
12+
'/**',
13+
'* Beep [boop][boop].',
14+
'*',
15+
'* [boop]: http://example.com',
16+
'*',
17+
'* Foo bar.',
18+
'*',
19+
'* @param {string} str - input value',
20+
'* @returns {string} output value',
21+
'*',
22+
'* @example',
23+
'* var out = beep( "boop" );',
24+
'* // returns "beepboop"',
25+
'*/',
26+
'function beep( str ) {',
27+
'\treturn "beep" + str;',
28+
'}'
29+
].join( '\n' );
30+
31+
// Register the ESLint rule:
32+
linter.defineRule( 'jsdoc-final-definition', rule );
33+
34+
// Lint the code:
35+
result = linter.verify( code, {
36+
'rules': {
37+
'jsdoc-final-definition': 'error'
38+
}
39+
});
40+
console.log( result );
41+
/* =>
42+
[
43+
{
44+
'ruleId': 'jsdoc-final-definition',
45+
'severity': 2,
46+
'message': 'Move definitions to the end of the description (after the node at line `5`)',
47+
'line': 4,
48+
'column': 3,
49+
'nodeType': null,
50+
'source': '* [boop]: http://example.com',
51+
'endLine': 14,
52+
'endColumn': 3
53+
}
54+
]
55+
*/
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
3+
/**
4+
* ESLint rule to require Markdown definitions in JSDoc descriptions be placed at the end of the description.
5+
*
6+
* @module @stdlib/_tools/eslint/rules/jsdoc-final-definition
7+
*
8+
* @example
9+
* var rule = require( '@stdlib/_tools/eslint/rules/jsdoc-final-definition' );
10+
*
11+
* console.log( rule );
12+
*/
13+
14+
// MODULES //
15+
16+
var main = require( './main.js' );
17+
18+
19+
// EXPORTS //
20+
21+
module.exports = main;
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
'use strict';
2+
3+
// MODULES //
4+
5+
var parseJSDoc = require( 'doctrine' ).parse;
6+
var remark = require( 'remark' );
7+
var remarkLint = require( 'remark-lint' );
8+
var remarkPlugin = require( 'remark-lint-final-definition' );
9+
var isObject = require( '@stdlib/assert/is-object' );
10+
var replace = require( '@stdlib/string/replace' );
11+
var findJSDoc = require( '@stdlib/_tools/eslint/utils/find-jsdoc' );
12+
13+
14+
// VARIABLES //
15+
16+
var DOPTS = {
17+
'sloppy': true,
18+
'unwrap': true
19+
};
20+
21+
22+
// MAIN //
23+
24+
/**
25+
* Rule to require Markdown definitions in JSDoc descriptions be placed at the end of the description.
26+
*
27+
* @param {Object} context - ESLint context
28+
* @returns {Object} validators
29+
*/
30+
function main( context ) {
31+
var source;
32+
var config;
33+
var lint;
34+
35+
config = {
36+
'plugins': [
37+
remarkLint,
38+
[ remarkPlugin, 'error' ]
39+
]
40+
};
41+
lint = remark().use( config ).processSync;
42+
source = context.getSourceCode();
43+
44+
return {
45+
'FunctionExpression:exit': validate,
46+
'FunctionDeclaration:exit': validate,
47+
'VariableDeclaration:exit': validate,
48+
'ExpressionStatement:exit': validate
49+
};
50+
51+
/**
52+
* Lints JSDoc descriptions.
53+
*
54+
* @private
55+
* @param {ASTNode} node - AST node
56+
*/
57+
function validate( node ) {
58+
var jsdoc;
59+
var vfile;
60+
var ast;
61+
62+
jsdoc = findJSDoc( source, node );
63+
if ( isObject( jsdoc ) ) {
64+
ast = parseJSDoc( jsdoc.value, DOPTS );
65+
if ( ast.description ) {
66+
vfile = lint( ast.description );
67+
if ( vfile.messages.length ) {
68+
reportErrors( vfile.messages, jsdoc.loc );
69+
}
70+
}
71+
}
72+
} // end FUNCTION validate()
73+
74+
/**
75+
* Reports Markdown lint errors.
76+
*
77+
* @private
78+
* @param {ObjectArray} errors - Markdown lint errors
79+
* @param {Object} location - JSDoc location information
80+
*/
81+
function reportErrors( errors, location ) {
82+
var err;
83+
var msg;
84+
var loc;
85+
var i;
86+
87+
for ( i = 0; i < errors.length; i++ ) {
88+
err = errors[ i ];
89+
msg = replace( err.message, 'file', 'description' ); // plugin assumes a "file"; we need to tailor to JSDoc description
90+
loc = copyLocationInfo( location );
91+
loc.start.line = err.location.start.line + 1; // Note: we assume `/**` is on its own line
92+
loc.start.column = err.location.start.column + 1; // Note: we assume that 1 space separates `*` from JSDoc description content (e.g., `* ## Beep`)
93+
report( msg, loc );
94+
}
95+
} // end FUNCTION reportErrors()
96+
97+
/**
98+
* Copies AST node location info.
99+
*
100+
* @private
101+
* @param {Object} loc - AST node location
102+
* @returns {Object} copied location info
103+
*/
104+
function copyLocationInfo( loc ) {
105+
return {
106+
'start': {
107+
'line': loc.start.line,
108+
'column': loc.start.column
109+
},
110+
'end': {
111+
'line': loc.end.line,
112+
'column': loc.end.column
113+
}
114+
};
115+
} // end FUNCTION copyLocationInfo()
116+
117+
/**
118+
* Reports an error message.
119+
*
120+
* @private
121+
* @param {string} msg - error message
122+
* @param {Object} loc - error location info
123+
*/
124+
function report( msg, loc ) {
125+
context.report({
126+
'node': null,
127+
'message': msg,
128+
'loc': loc
129+
});
130+
} // end FUNCTION report()
131+
} // end FUNCTION main()
132+
133+
134+
// EXPORTS //
135+
136+
module.exports = {
137+
'meta': {
138+
'docs': {
139+
'description': 'require Markdown definitions in JSDoc descriptions be placed at the end of the description'
140+
},
141+
'schema': []
142+
},
143+
'create': main
144+
};

0 commit comments

Comments
 (0)