eslint-plugin-crisp 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +19 -12
- package/package.json +2 -2
- package/recommended-vue.js +92 -0
- package/recommended.js +56 -19
- package/rules/align-jsdocs-params.js +78 -0
- package/rules/header-check.js +51 -0
- package/rules/header-comments-check.js +117 -0
- package/rules/jsdoc-enforce-classdesc.js +36 -0
- package/rules/methods-naming.js +1 -3
- package/rules/multiline-comment-end-backslash.js +0 -1
- package/rules/no-space-in-optional-arguments.js +21 -0
- package/rules/no-trailing-spaces.js +0 -1
- package/rules/no-var-in-blocks.js +25 -0
- package/rules/regex-in-constructor.js +0 -3
- package/rules/two-lines-between-class-members.js +0 -1
- package/rules/variable-names.js +94 -15
- package/rules/jsdoc-match-params.js +0 -56
package/index.js
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
configs: {
|
|
3
|
-
recommended: require(
|
|
3
|
+
recommended: require("./recommended"),
|
|
4
|
+
"recommended-vue": require("./recommended-vue")
|
|
4
5
|
},
|
|
5
6
|
rules: {
|
|
6
|
-
"jsdoc-match-params": require("./rules/jsdoc-match-params"),
|
|
7
7
|
"align-one-var": require("./rules/align-one-var"),
|
|
8
|
-
"
|
|
9
|
-
"
|
|
8
|
+
"align-requires": require("./rules/align-requires"),
|
|
9
|
+
"const": require("./rules/const"),
|
|
10
10
|
"constructor-variables": require("./rules/constructor-variables"),
|
|
11
|
+
"header-check": require("./rules/header-check"),
|
|
12
|
+
"header-comments-check": require("./rules/header-comments-check"),
|
|
13
|
+
"jsdoc-enforce-classdesc": require("./rules/jsdoc-enforce-classdesc"),
|
|
11
14
|
"methods-naming": require("./rules/methods-naming"),
|
|
12
|
-
"
|
|
13
|
-
"
|
|
15
|
+
"multiline-comment-end-backslash": require("./rules/multiline-comment-end-backslash"),
|
|
16
|
+
"new-line-after-block": require("./rules/new-line-after-block"),
|
|
14
17
|
"no-async": require("./rules/no-async"),
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
+
"no-trailing-spaces": require("./rules/no-trailing-spaces"),
|
|
19
|
+
"no-var-in-blocks": require("./rules/no-var-in-blocks"),
|
|
20
|
+
"no-space-in-optional-arguments": require("./rules/no-space-in-optional-arguments"),
|
|
21
|
+
"one-space-after-operator": require("./rules/one-space-after-operator"),
|
|
22
|
+
"regex-in-constructor": require("./rules/regex-in-constructor"),
|
|
18
23
|
"ternary-parenthesis": require("./rules/ternary-parenthesis"),
|
|
19
|
-
"
|
|
20
|
-
"
|
|
24
|
+
"multiline-comment-end-backslash": require("./rules/multiline-comment-end-backslash"),
|
|
25
|
+
"align-jsdocs-params": require("./rules/align-jsdocs-params"),
|
|
26
|
+
"two-lines-between-class-members": require("./rules/two-lines-between-class-members"),
|
|
27
|
+
"variable-names": require("./rules/variable-names")
|
|
21
28
|
}
|
|
22
|
-
};
|
|
29
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-crisp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Custom EsLint Rules for Crisp",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Crisp IM SAS",
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"doctrine": "3.0.0",
|
|
10
10
|
"eslint": "8.45.0",
|
|
11
|
-
"eslint-plugin-jsdoc": "
|
|
11
|
+
"eslint-plugin-jsdoc": "43.0.7"
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
env: {
|
|
3
|
+
es6: true,
|
|
4
|
+
node: true
|
|
5
|
+
},
|
|
6
|
+
|
|
7
|
+
plugins: [
|
|
8
|
+
"eslint-plugin-jsdoc"
|
|
9
|
+
],
|
|
10
|
+
|
|
11
|
+
extends: [
|
|
12
|
+
"plugin:jsdoc/recommended"
|
|
13
|
+
],
|
|
14
|
+
|
|
15
|
+
rules: {
|
|
16
|
+
// General JS rules
|
|
17
|
+
"no-eval": "error",
|
|
18
|
+
"no-console": "warn",
|
|
19
|
+
"no-debugger": "warn",
|
|
20
|
+
"no-unused-vars": "warn",
|
|
21
|
+
|
|
22
|
+
// JSDoc rules
|
|
23
|
+
"jsdoc/require-param-description": "off",
|
|
24
|
+
"jsdoc/check-indentation": "error",
|
|
25
|
+
"jsdoc/require-jsdoc": [
|
|
26
|
+
"error",
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
require: {
|
|
30
|
+
FunctionDeclaration: true,
|
|
31
|
+
MethodDefinition: true,
|
|
32
|
+
ClassDeclaration: true,
|
|
33
|
+
ArrowFunctionExpression: false,
|
|
34
|
+
FunctionExpression: false
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"jsdoc/sort-tags": [
|
|
39
|
+
"error",
|
|
40
|
+
|
|
41
|
+
{
|
|
42
|
+
tagSequence: [
|
|
43
|
+
{
|
|
44
|
+
tags: [
|
|
45
|
+
"private",
|
|
46
|
+
"protected",
|
|
47
|
+
"public",
|
|
48
|
+
|
|
49
|
+
"class",
|
|
50
|
+
|
|
51
|
+
"classdesc",
|
|
52
|
+
|
|
53
|
+
"param",
|
|
54
|
+
"return"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
|
|
61
|
+
// Crisp JSDoc rules
|
|
62
|
+
"crisp/jsdoc-enforce-classdesc": "error",
|
|
63
|
+
"crisp/align-jsdocs-params": "error",
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
// Crisp JS rules
|
|
67
|
+
"crisp/header-check": "error",
|
|
68
|
+
"crisp/header-comments-check": "error",
|
|
69
|
+
"crisp/methods-naming": "error",
|
|
70
|
+
"crisp/multiline-comment-end-backslash": "error",
|
|
71
|
+
"crisp/new-line-after-block": "error",
|
|
72
|
+
"crisp/no-async": "error",
|
|
73
|
+
"crisp/no-trailing-spaces": "error",
|
|
74
|
+
"crisp/one-space-after-operator": "error",
|
|
75
|
+
"crisp/regex-in-constructor": "error",
|
|
76
|
+
"crisp/variable-names": "error",
|
|
77
|
+
|
|
78
|
+
// Crisp Vue rules
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
settings: {
|
|
82
|
+
jsdoc: {
|
|
83
|
+
tagNamePreference: {
|
|
84
|
+
returns: "return"
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
preferredTypes: {
|
|
88
|
+
Function: "function"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
package/recommended.js
CHANGED
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
env: {
|
|
3
|
+
es6: true,
|
|
4
|
+
node: true
|
|
5
5
|
},
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
|
|
7
|
+
settings: {
|
|
8
|
+
jsdoc: {
|
|
9
|
+
tagNamePreference: {
|
|
10
|
+
returns: "return"
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
preferredTypes: {
|
|
14
|
+
Function: "function"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
plugins: [
|
|
20
|
+
"eslint-plugin-jsdoc",
|
|
21
|
+
],
|
|
22
|
+
|
|
23
|
+
extends: [
|
|
24
|
+
"plugin:jsdoc/recommended"
|
|
8
25
|
],
|
|
9
|
-
|
|
26
|
+
|
|
27
|
+
rules: {
|
|
28
|
+
// General JS rules
|
|
29
|
+
"no-eval": "error",
|
|
30
|
+
"no-console": "warn",
|
|
31
|
+
"no-debugger": "warn",
|
|
32
|
+
"no-unused-vars": ["warn", { "argsIgnorePattern": "^request" }],
|
|
10
33
|
"indent": "off",
|
|
11
34
|
"linebreak-style": ["error", "unix"],
|
|
12
35
|
"quotes": ["error", "double", { "avoidEscape": true, "allowTemplateLiterals": true }],
|
|
@@ -14,13 +37,38 @@ module.exports = {
|
|
|
14
37
|
"max-len": ["error", 80],
|
|
15
38
|
"comma-dangle": ["error", "never"],
|
|
16
39
|
"arrow-parens": ["error", "always"],
|
|
17
|
-
|
|
40
|
+
|
|
41
|
+
// Crisp JSDoc rules
|
|
42
|
+
"crisp/jsdoc-enforce-classdesc": "error",
|
|
43
|
+
"crisp/align-jsdocs-params": "error",
|
|
44
|
+
|
|
45
|
+
// JSDoc rules
|
|
46
|
+
"jsdoc/require-param-description": "off",
|
|
47
|
+
"jsdoc/newline-after-description": "off",
|
|
48
|
+
"jsdoc/require-jsdoc": [
|
|
49
|
+
"error",
|
|
50
|
+
|
|
51
|
+
{
|
|
52
|
+
require: {
|
|
53
|
+
"FunctionDeclaration": true,
|
|
54
|
+
"MethodDefinition": true,
|
|
55
|
+
"ClassDeclaration": true,
|
|
56
|
+
"ArrowFunctionExpression": false,
|
|
57
|
+
"FunctionExpression": false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
|
|
62
|
+
// Crisp rules
|
|
63
|
+
"crisp/align-one-var": "error",
|
|
18
64
|
"crisp/multiline-comment-end-backslash": "error",
|
|
19
65
|
"crisp/const": "error",
|
|
20
|
-
"crisp/regex-in-constructor":
|
|
66
|
+
"crisp/regex-in-constructor": "error",
|
|
21
67
|
"crisp/align-requires": "error",
|
|
22
68
|
"crisp/two-lines-between-class-members": "error",
|
|
23
69
|
"crisp/no-async": "error",
|
|
70
|
+
"crisp/no-var-in-blocks": "error",
|
|
71
|
+
"crisp/no-space-in-optional-arguments": "error",
|
|
24
72
|
"crisp/methods-naming": "error",
|
|
25
73
|
"crisp/new-line-after-block": "error",
|
|
26
74
|
"crisp/one-space-after-operator": "error",
|
|
@@ -29,20 +77,9 @@ module.exports = {
|
|
|
29
77
|
"crisp/variable-names": ["error", {
|
|
30
78
|
"variableExceptions": ["fn"]
|
|
31
79
|
}],
|
|
32
|
-
"crisp/jsdoc-match-params": ["error", { "exceptions": ["constructor"] }],
|
|
33
|
-
|
|
34
80
|
"crisp/constructor-variables": ["error", {
|
|
35
81
|
"filenameExceptions": ["app.js"],
|
|
36
82
|
"variableExceptions": ["client"]
|
|
37
|
-
}],
|
|
38
|
-
"jsdoc/require-jsdoc": ["error", {
|
|
39
|
-
"require": {
|
|
40
|
-
"FunctionDeclaration": true,
|
|
41
|
-
"MethodDefinition": true,
|
|
42
|
-
"ClassDeclaration": true,
|
|
43
|
-
"ArrowFunctionExpression": false,
|
|
44
|
-
"FunctionExpression": false
|
|
45
|
-
}
|
|
46
83
|
}]
|
|
47
84
|
}
|
|
48
85
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'layout',
|
|
4
|
+
docs: {
|
|
5
|
+
description: 'enforce alignment for JSDocs',
|
|
6
|
+
category: 'Stylistic Issues',
|
|
7
|
+
recommended: false,
|
|
8
|
+
},
|
|
9
|
+
fixable: 'whitespace',
|
|
10
|
+
},
|
|
11
|
+
create: function(context) {
|
|
12
|
+
return {
|
|
13
|
+
Program: function(node) {
|
|
14
|
+
const comments = context.getSourceCode().getAllComments(node);
|
|
15
|
+
|
|
16
|
+
comments
|
|
17
|
+
.filter(comment => comment.type === 'Block' && comment.value.startsWith('*'))
|
|
18
|
+
.forEach(jsdocComment => {
|
|
19
|
+
const lines = jsdocComment.value.split('\n');
|
|
20
|
+
let bracePos = -1, descPos = -1;
|
|
21
|
+
let lineNum = jsdocComment.loc.start.line;
|
|
22
|
+
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
lineNum++;
|
|
25
|
+
const paramMatch = line.match(/@param\s*{(\S*)}[\s\t]+(\S+)\s*(.*)/);
|
|
26
|
+
const returnMatch = line.match(/@return\s*{(\S*)}\s*(.*)/);
|
|
27
|
+
|
|
28
|
+
let match = null;
|
|
29
|
+
|
|
30
|
+
if (paramMatch) {
|
|
31
|
+
match = paramMatch;
|
|
32
|
+
} else if (returnMatch) {
|
|
33
|
+
match = returnMatch;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (match) {
|
|
37
|
+
const newBracePos = match.index + match[0].indexOf('{');
|
|
38
|
+
let descStart = match[0].substring(match[0].lastIndexOf('}') + 1).search(/\S/);
|
|
39
|
+
|
|
40
|
+
// If description is undefined, skip the description alignment check
|
|
41
|
+
if (match[match.length - 1] === 'undefined') {
|
|
42
|
+
descStart = -1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (descStart !== -1) {
|
|
46
|
+
const newDescPos = match.index + match[0].lastIndexOf('}') + 1 + descStart + 1; // +1 for ESLint column index
|
|
47
|
+
|
|
48
|
+
if (descPos === -1) {
|
|
49
|
+
descPos = newDescPos;
|
|
50
|
+
} else if (descPos !== newDescPos) {
|
|
51
|
+
context.report({
|
|
52
|
+
node: jsdocComment,
|
|
53
|
+
message: `In JSDoc at line ${lineNum}, the description is misaligned. Found at column ${newDescPos}, but expected column ${descPos}.`,
|
|
54
|
+
loc: {
|
|
55
|
+
start: { line: lineNum, column: newDescPos },
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (bracePos === -1) {
|
|
62
|
+
bracePos = newBracePos;
|
|
63
|
+
} else if (bracePos !== newBracePos) {
|
|
64
|
+
context.report({
|
|
65
|
+
node: jsdocComment,
|
|
66
|
+
message: `In JSDoc at line ${lineNum}, the type brace is misaligned. Found at column ${newBracePos + 1}, but expected column ${bracePos + 1}.`,
|
|
67
|
+
loc: {
|
|
68
|
+
start: { line: lineNum, column: newBracePos + 1 },
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
meta: {
|
|
6
|
+
type: "suggestion",
|
|
7
|
+
docs: {
|
|
8
|
+
description: "Enforce file header",
|
|
9
|
+
category: "Best Practices",
|
|
10
|
+
recommended: false,
|
|
11
|
+
},
|
|
12
|
+
fixable: null, // This rule is not auto-fixable
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
create(context) {
|
|
16
|
+
return {
|
|
17
|
+
Program(node) {
|
|
18
|
+
const fileName = context.getFilename();
|
|
19
|
+
const fileContent = fs.readFileSync(path.resolve(fileName), "utf8");
|
|
20
|
+
|
|
21
|
+
// Determine the file extension
|
|
22
|
+
const fileExtension = path.extname(fileName);
|
|
23
|
+
|
|
24
|
+
// Base header comment pattern
|
|
25
|
+
let basePattern = "\n \\* This file is part of .+\\n \\*\\n \\* Copyright \\(c\\) \\d{4} Crisp IM SAS\\n \\* All rights belong to Crisp IM SAS\\n ";
|
|
26
|
+
|
|
27
|
+
let headerStart, headerEnd;
|
|
28
|
+
|
|
29
|
+
// Set the headerFormat RegExp according to the file type
|
|
30
|
+
if (fileExtension === ".vue") {
|
|
31
|
+
headerStart = "<!--";
|
|
32
|
+
headerEnd = "-->";
|
|
33
|
+
} else if (fileExtension === ".js") {
|
|
34
|
+
headerStart = "\/\\*";
|
|
35
|
+
headerEnd = "\\*\/";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Construct the final headerFormat RegExp
|
|
39
|
+
const headerFormat = headerStart && headerEnd ? new RegExp("^" + headerStart + basePattern + headerEnd) : null;
|
|
40
|
+
|
|
41
|
+
// Only check the header format if it's a .vue or .js file
|
|
42
|
+
if (headerFormat && !headerFormat.test(fileContent)) {
|
|
43
|
+
context.report({
|
|
44
|
+
node,
|
|
45
|
+
message: "File must start with the proper header.",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "suggestion",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "Enforce file header comments",
|
|
6
|
+
category: "Best Practices",
|
|
7
|
+
recommended: false,
|
|
8
|
+
},
|
|
9
|
+
fixable: null, // This rule is not auto-fixable
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
create(context) {
|
|
13
|
+
const filename = context.getFilename();
|
|
14
|
+
|
|
15
|
+
// Only apply this rule to .js files
|
|
16
|
+
if (!filename.endsWith(".js")) {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let lastNodeType = null;
|
|
21
|
+
let groupStart = null;
|
|
22
|
+
|
|
23
|
+
// This function formats the section string into a comment block header
|
|
24
|
+
const COMMENT_HEADER_FORMAT = (section) => {
|
|
25
|
+
return `/**************************************************************************\n * ${section.toUpperCase()}\n ***************************************************************************/`;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function checkGroup(nodeType, startNode) {
|
|
29
|
+
// If a variable is not declared at the top level, don't enforce the comment
|
|
30
|
+
if (nodeType === "VariableDeclaration" && startNode.parent.type !== "Program") {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Find the nearest Block Comment before the startNode
|
|
35
|
+
const tokens = context.getSourceCode().getTokensBefore(startNode, {includeComments: true});
|
|
36
|
+
const comment = tokens.reverse().find(token => token.type === "Block");
|
|
37
|
+
|
|
38
|
+
// Different types of nodes require different comment blocks
|
|
39
|
+
switch (nodeType) {
|
|
40
|
+
case "ImportDeclaration": {
|
|
41
|
+
if (!comment || `/*${comment.value.trim()}*/` !== COMMENT_HEADER_FORMAT("IMPORTS")) {
|
|
42
|
+
context.report({
|
|
43
|
+
node: startNode,
|
|
44
|
+
message: "Import group must be preceded by a 'IMPORTS' comment block",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
case "VariableDeclaration": {
|
|
52
|
+
if (!comment || `/*${comment.value.trim()}*/` !== COMMENT_HEADER_FORMAT("CONSTANTS")) {
|
|
53
|
+
context.report({
|
|
54
|
+
node: startNode,
|
|
55
|
+
message: "Variable group must be preceded by a 'CONSTANTS' comment block",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (startNode.declarations.some(d => d.init && d.init.regex)) {
|
|
60
|
+
if (!comment || `/*${comment.value.trim()}*/` !== COMMENT_HEADER_FORMAT("INSTANCES")) {
|
|
61
|
+
context.report({
|
|
62
|
+
node: startNode,
|
|
63
|
+
message: "Regex group must be preceded by a 'INSTANCES' comment block",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
case "NewExpression": {
|
|
72
|
+
if (!comment || `/*${comment.value.trim()}*/` !== COMMENT_HEADER_FORMAT("INSTANCES")) {
|
|
73
|
+
context.report({
|
|
74
|
+
node: startNode,
|
|
75
|
+
message: "Regex group must be preceded by a 'INSTANCES' comment block",
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case "ExportDefaultDeclaration": {
|
|
83
|
+
// Only enforce the 'EXPORTS' comment block if it's the last statement in the file
|
|
84
|
+
if (startNode !== context.getSourceCode().ast.body.slice(-1)[0] && (!comment || `/*${comment.value.trim()}*/` !== COMMENT_HEADER_FORMAT("EXPORTS"))) {
|
|
85
|
+
context.report({
|
|
86
|
+
node: startNode,
|
|
87
|
+
message: "Export group must be preceded by a 'EXPORTS' comment block",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
":statement": (node) => {
|
|
98
|
+
const nodeType = node.type;
|
|
99
|
+
// If the type of node has changed since last time, check the group starting with the last node
|
|
100
|
+
if (nodeType !== lastNodeType) {
|
|
101
|
+
if (groupStart) {
|
|
102
|
+
checkGroup(lastNodeType, groupStart);
|
|
103
|
+
}
|
|
104
|
+
groupStart = node;
|
|
105
|
+
}
|
|
106
|
+
lastNodeType = nodeType;
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
"Program:exit": () => {
|
|
110
|
+
// When the program exits, check the group starting with the last node
|
|
111
|
+
if (groupStart) {
|
|
112
|
+
checkGroup(lastNodeType, groupStart);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "suggestion",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "Enforce @classdesc in JSDoc class headers",
|
|
6
|
+
category: "Best Practices",
|
|
7
|
+
recommended: true,
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
create(context) {
|
|
12
|
+
return {
|
|
13
|
+
ClassDeclaration(node) {
|
|
14
|
+
const jsdoc = context.getSourceCode().getJSDocComment(node);
|
|
15
|
+
|
|
16
|
+
if (jsdoc) {
|
|
17
|
+
const hasClassdesc = /@classdesc/.test(jsdoc.value);
|
|
18
|
+
if (!hasClassdesc) {
|
|
19
|
+
context.report({
|
|
20
|
+
node,
|
|
21
|
+
message: "JSDoc class header should include @classdesc"
|
|
22
|
+
});
|
|
23
|
+
} else {
|
|
24
|
+
const hasNonEmptyDescription = /@classdesc\s+[^*\s]/.test(jsdoc.value);
|
|
25
|
+
if (!hasNonEmptyDescription) {
|
|
26
|
+
context.report({
|
|
27
|
+
node,
|
|
28
|
+
message: "The @classdesc tag in JSDoc class header should have a non-empty description",
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
};
|
package/rules/methods-naming.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// File: eslint-plugin-custom/private-public-methods.js
|
|
2
|
-
|
|
3
1
|
module.exports = {
|
|
4
2
|
meta: {
|
|
5
3
|
type: "problem",
|
|
@@ -16,7 +14,7 @@ module.exports = {
|
|
|
16
14
|
MethodDefinition: function(node) {
|
|
17
15
|
const commentsBefore = context.getCommentsBefore(node);
|
|
18
16
|
|
|
19
|
-
const jsDocComment = commentsBefore.find(comment =>
|
|
17
|
+
const jsDocComment = commentsBefore.find(comment =>
|
|
20
18
|
comment.type === "Block" && comment.value.startsWith("*")
|
|
21
19
|
);
|
|
22
20
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
create: function(context) {
|
|
3
|
+
return {
|
|
4
|
+
'FunctionDeclaration, FunctionExpression, ArrowFunctionExpression, MethodDefinition': function(node) {
|
|
5
|
+
(node.params || []).forEach(param => {
|
|
6
|
+
if (param.type === 'AssignmentPattern') {
|
|
7
|
+
const sourceCode = context.getSourceCode();
|
|
8
|
+
const operatorToken = sourceCode.getFirstTokenBetween(param.left, param.right, token => token.value === '=');
|
|
9
|
+
|
|
10
|
+
if (sourceCode.getTokenBefore(operatorToken).end !== operatorToken.start || sourceCode.getTokenAfter(operatorToken).start !== operatorToken.end) {
|
|
11
|
+
context.report({
|
|
12
|
+
node,
|
|
13
|
+
message: 'There should be no space before or after = in optional parameters'
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
create: function (context) {
|
|
3
|
+
return {
|
|
4
|
+
'FunctionDeclaration VariableDeclaration[kind="var"], FunctionExpression VariableDeclaration[kind="var"], ArrowFunctionExpression VariableDeclaration[kind="var"], MethodDefinition VariableDeclaration[kind="var"], ClassDeclaration VariableDeclaration[kind="var"]': function (node) {
|
|
5
|
+
var isRequire = node.declarations.some(function (declaration) {
|
|
6
|
+
if (declaration.init) {
|
|
7
|
+
if (declaration.init.type === 'CallExpression' && declaration.init.callee.name === 'require') {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
if (declaration.init.type === 'MemberExpression') {
|
|
11
|
+
return declaration.init.object.type === 'CallExpression' && declaration.init.object.callee.name === 'require';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
});
|
|
16
|
+
if (!isRequire) {
|
|
17
|
+
context.report({
|
|
18
|
+
node: node,
|
|
19
|
+
message: "Unexpected 'var' declaration inside function, method, or class block."
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
package/rules/variable-names.js
CHANGED
|
@@ -4,53 +4,132 @@ module.exports = {
|
|
|
4
4
|
docs: {
|
|
5
5
|
description: "enforce that variables defined within a method start with '_', except for parameters",
|
|
6
6
|
category: "Stylistic Issues",
|
|
7
|
-
recommended: false
|
|
7
|
+
recommended: false
|
|
8
8
|
},
|
|
9
|
+
|
|
9
10
|
schema: [{
|
|
10
11
|
type: "object",
|
|
11
12
|
properties: {
|
|
12
13
|
variableExceptions: {
|
|
13
14
|
type: "array",
|
|
14
15
|
items: { type: "string" },
|
|
15
|
-
uniqueItems: true
|
|
16
|
+
uniqueItems: true
|
|
16
17
|
}
|
|
17
18
|
},
|
|
18
|
-
additionalProperties: false
|
|
19
|
-
}]
|
|
19
|
+
additionalProperties: false
|
|
20
|
+
}]
|
|
20
21
|
},
|
|
22
|
+
|
|
21
23
|
create(context) {
|
|
22
24
|
const options = context.options[0] || {};
|
|
23
25
|
const variableExceptions = options.variableExceptions || [];
|
|
24
26
|
|
|
25
|
-
function checkDeclaration(node
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
function checkDeclaration(node) {
|
|
28
|
+
if (!node) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
switch(node.type) {
|
|
33
|
+
// In case of a variable declaration, it checks if the variable name \
|
|
34
|
+
// starts with "_" or is an exception
|
|
35
|
+
case "VariableDeclaration": {
|
|
36
|
+
node.declarations.forEach((declaration) => {
|
|
29
37
|
if (declaration.id.name && !declaration.id.name.startsWith("_") && !variableExceptions.includes(declaration.id.name)) {
|
|
30
38
|
context.report({
|
|
31
39
|
node: declaration,
|
|
32
|
-
message: `Variables defined within a method should start with
|
|
40
|
+
message: `Variables defined within a method should start with "_" ({${declaration.id.name}})`,
|
|
33
41
|
});
|
|
34
42
|
}
|
|
35
43
|
});
|
|
44
|
+
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// BlockStatement and Program nodes contain lists of statements \
|
|
49
|
+
// (node.body), so we check each statement
|
|
50
|
+
case "BlockStatement":
|
|
51
|
+
case "Program": {
|
|
52
|
+
node.body.forEach(checkDeclaration);
|
|
53
|
+
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// IfStatement nodes have a consequent (then), and an \
|
|
58
|
+
// optional alternate (else) to be checked
|
|
59
|
+
case "IfStatement": {
|
|
60
|
+
checkDeclaration(node.consequent);
|
|
61
|
+
|
|
62
|
+
if (node.alternate) {
|
|
63
|
+
checkDeclaration(node.alternate);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Loops nodes contain a body with statements to be checked
|
|
70
|
+
case "ForStatement":
|
|
71
|
+
case "WhileStatement":
|
|
72
|
+
case "DoWhileStatement":
|
|
73
|
+
case "ForInStatement":
|
|
74
|
+
case "ForOfStatement": {
|
|
75
|
+
if (node.body) {
|
|
76
|
+
checkDeclaration(node.body);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check the loop condition itself
|
|
80
|
+
checkDeclaration(node.left);
|
|
81
|
+
|
|
82
|
+
break;
|
|
36
83
|
}
|
|
37
|
-
|
|
84
|
+
|
|
85
|
+
// SwitchStatement nodes contain an array of SwitchCase nodes that \
|
|
86
|
+
// represent each case in the switch statement
|
|
87
|
+
case "SwitchStatement": {
|
|
88
|
+
node.cases.forEach(checkDeclaration);
|
|
89
|
+
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// SwitchCase nodes contain a consequent which is an array of \
|
|
94
|
+
// Statements to be checked
|
|
95
|
+
case "SwitchCase": {
|
|
96
|
+
node.consequent.forEach(checkDeclaration);
|
|
97
|
+
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
default: {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
38
105
|
}
|
|
39
106
|
|
|
40
107
|
return {
|
|
108
|
+
// For each property in an object, if it's a function / arrow function, \
|
|
109
|
+
// check its body (usefull for Vue.js components, in which methods and \
|
|
110
|
+
// computed are defined as properties of a parent object)
|
|
111
|
+
Property(node) {
|
|
112
|
+
if (node.value.type === "FunctionExpression" ||
|
|
113
|
+
node.value.type === "ArrowFunctionExpression") {
|
|
114
|
+
checkDeclaration(node);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
|
|
41
118
|
MethodDefinition(node) {
|
|
42
|
-
checkDeclaration(node
|
|
119
|
+
checkDeclaration(node.value.body);
|
|
43
120
|
},
|
|
121
|
+
|
|
44
122
|
ArrowFunctionExpression(node) {
|
|
45
123
|
if (node.body.type === "BlockStatement") {
|
|
46
|
-
checkDeclaration(node
|
|
124
|
+
checkDeclaration(node.body);
|
|
47
125
|
}
|
|
48
126
|
},
|
|
127
|
+
|
|
49
128
|
FunctionExpression(node) {
|
|
50
129
|
if (node.body.type === "BlockStatement") {
|
|
51
|
-
checkDeclaration(node
|
|
130
|
+
checkDeclaration(node.body);
|
|
52
131
|
}
|
|
53
|
-
}
|
|
132
|
+
}
|
|
54
133
|
};
|
|
55
|
-
}
|
|
134
|
+
}
|
|
56
135
|
};
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
const doctrine = require("doctrine");
|
|
2
|
-
|
|
3
|
-
module.exports = {
|
|
4
|
-
meta: {
|
|
5
|
-
type: "problem",
|
|
6
|
-
docs: {
|
|
7
|
-
description: "enforce JSDoc and function parameter names match",
|
|
8
|
-
category: "Possible Errors",
|
|
9
|
-
recommended: true,
|
|
10
|
-
},
|
|
11
|
-
},
|
|
12
|
-
|
|
13
|
-
create(context) {
|
|
14
|
-
const sourceCode = context.getSourceCode();
|
|
15
|
-
|
|
16
|
-
function getJSDocComment(node) {
|
|
17
|
-
const commentsBefore = sourceCode.getCommentsBefore(node);
|
|
18
|
-
|
|
19
|
-
const jsDocComment = commentsBefore.find(comment =>
|
|
20
|
-
comment.type === "Block" && comment.value.startsWith("*")
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
if (jsDocComment) {
|
|
24
|
-
const parsed = doctrine.parse(jsDocComment.value, { unwrap: true });
|
|
25
|
-
const jsDocParams = parsed.tags.filter(tag => tag.title === "param").map(tag => tag.name);
|
|
26
|
-
return jsDocParams;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function checkParameters(node) {
|
|
33
|
-
const jsDocParams = getJSDocComment(node);
|
|
34
|
-
|
|
35
|
-
if (!jsDocParams) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const funcParams = node.value.params.map(param => param.type === "Identifier" ? param.name : param.left.name);
|
|
40
|
-
|
|
41
|
-
for (let i = 0; i < jsDocParams.length; i++) {
|
|
42
|
-
if (jsDocParams[i] !== funcParams[i]) {
|
|
43
|
-
context.report({
|
|
44
|
-
node: node,
|
|
45
|
-
message: `JSDoc @param name does not match function parameter name. Expected '${funcParams[i]}' but got '${jsDocParams[i]}'`
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
FunctionDeclaration: checkParameters,
|
|
53
|
-
MethodDefinition: checkParameters,
|
|
54
|
-
};
|
|
55
|
-
},
|
|
56
|
-
};
|