Skip to content

Commit 2e6fcae

Browse files
committed
Add rule to enforce a specific emphasis marker
1 parent 19688d8 commit 2e6fcae

9 files changed

Lines changed: 998 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)