Skip to content
This repository was archived by the owner on May 16, 2020. It is now read-only.

Commit 385c3dc

Browse files
feat: add in custom valid-jsdoc rule in plugin @atlauncher/eslint-plugin-atlauncher
1 parent 1638e23 commit 385c3dc

File tree

9 files changed

+357
-11
lines changed

9 files changed

+357
-11
lines changed

packages/eslint-config-atlauncher/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const rules = [
88
'./rules/ecmascript-6',
99

1010
// eslint plugins
11+
'./rules/plugin-atlauncher',
1112
'./rules/plugin-import',
1213
'./rules/plugin-jsx-a11y',
1314
'./rules/plugin-promise',
@@ -21,7 +22,8 @@ module.exports = {
2122
'react',
2223
'promise',
2324
'no-empty-blocks',
24-
'filenames'
25+
'filenames',
26+
'@atlauncher/atlauncher'
2527
],
2628
extends: rules,
2729
env: {

packages/eslint-config-atlauncher/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
},
2525
"homepage": "https://github.com/ATLauncher/style-guide",
2626
"devDependencies": {
27+
"@atlauncher/eslint-plugin-atlauncher": "^0.1.0",
2728
"babel-eslint": "^7.2.1",
2829
"eslint": "^3.19.0",
2930
"eslint-plugin-import": "^2.2.0",
@@ -33,6 +34,7 @@
3334
"eslint-plugin-react": "^6.10.3"
3435
},
3536
"peerDependencies": {
37+
"@atlauncher/eslint-plugin-atlauncher": "^0.1.0",
3638
"babel-eslint": "^7.2.1",
3739
"eslint": "^3.19.0",
3840
"eslint-plugin-import": "^2.2.0",
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module.exports = {
2+
rules: {
3+
// warn when not using jsdoc unless function name matches one in the ignore list
4+
'atlauncher/require-jsdoc': ['warn', {
5+
require: {
6+
FunctionDeclaration: true,
7+
MethodDefinition: true,
8+
ClassDeclaration: true,
9+
ArrowFunctionExpression: true
10+
},
11+
ignore: [
12+
'render'
13+
]
14+
}]
15+
}
16+
};

packages/eslint-config-atlauncher/rules/stylistic-issues.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -187,16 +187,6 @@ module.exports = {
187187
allowTemplateLiterals: true
188188
}],
189189

190-
// warn when not using jsdoc unless function name matches one in the ignore list
191-
'require-jsdoc': ['warn', {
192-
require: {
193-
FunctionDeclaration: true,
194-
MethodDefinition: true,
195-
ClassDeclaration: true,
196-
ArrowFunctionExpression: true
197-
}
198-
}],
199-
200190
// require semi colons
201191
'semi': 'error',
202192

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# @atlauncher/eslint-plugin-atlauncher
2+
3+
Custom ESLint rules for use with eslint-config-atlauncher
4+
5+
## Installation
6+
7+
You'll first need to install [ESLint](http://eslint.org):
8+
9+
```
10+
$ npm i eslint --save-dev
11+
```
12+
13+
Next, install `@atlauncher/eslint-plugin-atlauncher`:
14+
15+
```
16+
$ npm install @atlauncher/eslint-plugin-atlauncher --save-dev
17+
```
18+
19+
**Note:** If you installed ESLint globally (using the `-g` flag) then you must also install `@atlauncher/eslint-plugin-atlauncher` globally.
20+
21+
## Usage
22+
23+
Add `@atlauncher/atlauncher` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix:
24+
25+
```json
26+
{
27+
"plugins": [
28+
"@atlauncher/atlauncher"
29+
]
30+
}
31+
```
32+
33+
Then configure the rules you want to use under the rules section.
34+
35+
```json
36+
{
37+
"rules": {
38+
"atlauncher/rule-name": 2
39+
}
40+
}
41+
```
42+
43+
## Supported Rules
44+
45+
* atlauncher/require-jsdoc - this is the same as the ESLint 'require-jsdoc' rule, but provides an extra 'ignore' option
46+
for function/method/class names to ignore
47+
48+
49+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* @fileoverview Custom ESLint rules for use with eslint-config-atlauncher
3+
* @author Ryan Dowling
4+
*/
5+
"use strict";
6+
7+
//------------------------------------------------------------------------------
8+
// Requirements
9+
//------------------------------------------------------------------------------
10+
11+
var requireIndex = require("requireindex");
12+
13+
//------------------------------------------------------------------------------
14+
// Plugin Definition
15+
//------------------------------------------------------------------------------
16+
17+
18+
// import all rules in lib/rules
19+
module.exports.rules = requireIndex(__dirname + "/rules");
20+
21+
22+
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/**
2+
* @fileoverview Ensures functions have valid JSDoc comments
3+
* @author Ryan Dowling
4+
*/
5+
"use strict";
6+
7+
const astUtils = require("eslint");
8+
9+
//------------------------------------------------------------------------------
10+
// Rule Definition
11+
//------------------------------------------------------------------------------
12+
13+
module.exports = {
14+
meta: {
15+
docs: {
16+
description: "Ensures functions have JSDoc comments",
17+
category: "Stylistic Issues",
18+
recommended: false
19+
},
20+
21+
schema: [
22+
{
23+
type: "object",
24+
properties: {
25+
require: {
26+
type: "object",
27+
properties: {
28+
ClassDeclaration: {
29+
type: "boolean"
30+
},
31+
MethodDefinition: {
32+
type: "boolean"
33+
},
34+
FunctionDeclaration: {
35+
type: "boolean"
36+
},
37+
ArrowFunctionExpression: {
38+
type: "boolean"
39+
}
40+
},
41+
additionalProperties: false
42+
},
43+
ignore: {
44+
type: "array",
45+
items: {
46+
type: "string"
47+
},
48+
uniqueItems: true
49+
}
50+
},
51+
additionalProperties: false
52+
}
53+
]
54+
},
55+
56+
create: function(context) {
57+
const source = context.getSourceCode();
58+
const DEFAULT_OPTIONS = {
59+
FunctionDeclaration: true,
60+
MethodDefinition: false,
61+
ClassDeclaration: false
62+
};
63+
const options = Object.assign(DEFAULT_OPTIONS, context.options[0] && context.options[0].require || {});
64+
const ignore = context.options[0] && context.options[0].ignore || [];
65+
66+
/**
67+
* Report the error message
68+
* @param {ASTNode} node node to report
69+
* @returns {void}
70+
*/
71+
function report(node) {
72+
context.report({ node, message: "Missing JSDoc comment." });
73+
}
74+
75+
/**
76+
* Check if the jsdoc comment is present for class methods
77+
* @param {ASTNode} node node to examine
78+
* @returns {void}
79+
*/
80+
function checkClassMethodJsDoc(node) {
81+
if (node.parent.type === "MethodDefinition") {
82+
if (isIgnored(node)) {
83+
return;
84+
}
85+
86+
const jsdocComment = source.getJSDocComment(node);
87+
88+
if (!jsdocComment) {
89+
report(node);
90+
}
91+
}
92+
}
93+
94+
/**
95+
* Check if this node is in the ignore list or not
96+
* @param {ASTNode} node node to examine
97+
* @returns {boolean|null}
98+
*/
99+
function isIgnored(node) {
100+
const name = getNodeName(node);
101+
102+
return name !== null && ignore.indexOf(name) !== -1;
103+
}
104+
105+
/**
106+
* Get the name of this node
107+
* @param {ASTNode} node node to examine
108+
* @returns {string}
109+
*/
110+
function getNodeName(node) {
111+
if (node.id) {
112+
return node.id.name;
113+
} else {
114+
const name = getStaticPropertyName(node.parent);
115+
116+
if (name) {
117+
return name;
118+
}
119+
}
120+
121+
return null;
122+
}
123+
124+
125+
/**
126+
* Gets the property name of a given node.
127+
* The node can be a MemberExpression, a Property, or a MethodDefinition.
128+
*
129+
* If the name is dynamic, this returns `null`.
130+
*
131+
* For examples:
132+
*
133+
* a.b // => "b"
134+
* a["b"] // => "b"
135+
* a['b'] // => "b"
136+
* a[`b`] // => "b"
137+
* a[100] // => "100"
138+
* a[b] // => null
139+
* a["a" + "b"] // => null
140+
* a[tag`b`] // => null
141+
* a[`${b}`] // => null
142+
*
143+
* let a = {b: 1} // => "b"
144+
* let a = {["b"]: 1} // => "b"
145+
* let a = {['b']: 1} // => "b"
146+
* let a = {[`b`]: 1} // => "b"
147+
* let a = {[100]: 1} // => "100"
148+
* let a = {[b]: 1} // => null
149+
* let a = {["a" + "b"]: 1} // => null
150+
* let a = {[tag`b`]: 1} // => null
151+
* let a = {[`${b}`]: 1} // => null
152+
*
153+
* @param {ASTNode} node - The node to get.
154+
* @returns {string|null} The property name if static. Otherwise, null.
155+
*/
156+
function getStaticPropertyName(node) {
157+
let prop;
158+
159+
switch (node && node.type) {
160+
case "Property":
161+
case "MethodDefinition":
162+
prop = node.key;
163+
break;
164+
165+
case "MemberExpression":
166+
prop = node.property;
167+
break;
168+
169+
// no default
170+
}
171+
172+
switch (prop && prop.type) {
173+
case "Literal":
174+
return String(prop.value);
175+
176+
case "TemplateLiteral":
177+
if (prop.expressions.length === 0 && prop.quasis.length === 1) {
178+
return prop.quasis[0].value.cooked;
179+
}
180+
break;
181+
182+
case "Identifier":
183+
if (!node.computed) {
184+
return prop.name;
185+
}
186+
break;
187+
188+
// no default
189+
}
190+
191+
return null;
192+
}
193+
194+
/**
195+
* Check if the jsdoc comment is present or not.
196+
* @param {ASTNode} node node to examine
197+
* @returns {void}
198+
*/
199+
function checkJsDoc(node) {
200+
if (isIgnored(node)) {
201+
return;
202+
}
203+
204+
const jsdocComment = source.getJSDocComment(node);
205+
206+
if (!jsdocComment) {
207+
report(node);
208+
}
209+
}
210+
211+
return {
212+
FunctionDeclaration(node) {
213+
if (options.FunctionDeclaration) {
214+
checkJsDoc(node);
215+
}
216+
},
217+
FunctionExpression(node) {
218+
if (options.MethodDefinition) {
219+
checkClassMethodJsDoc(node);
220+
}
221+
},
222+
ClassDeclaration(node) {
223+
if (options.ClassDeclaration) {
224+
checkJsDoc(node);
225+
}
226+
},
227+
ArrowFunctionExpression(node) {
228+
if (options.ArrowFunctionExpression && node.parent.type === "VariableDeclarator") {
229+
checkJsDoc(node);
230+
}
231+
}
232+
};
233+
}
234+
};

0 commit comments

Comments
 (0)