Skip to content

Commit 00cde5f

Browse files
committed
Add rule to prevent use of emphasis in place of a heading
1 parent 7b0b16d commit 00cde5f

9 files changed

Lines changed: 911 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)