eslint-plugin-crisp 1.0.0
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/.github/workflows/build.yml +31 -0
- package/README.md +2 -0
- package/index.js +22 -0
- package/package.json +13 -0
- package/recommended.js +44 -0
- package/rules/align-one-var.js +70 -0
- package/rules/align-requires.js +36 -0
- package/rules/const.js +27 -0
- package/rules/constructor-variables.js +40 -0
- package/rules/jsdoc-match-params.js +56 -0
- package/rules/methods-naming.js +55 -0
- package/rules/multiline-comment-end-backslash.js +33 -0
- package/rules/new-line-after-block.js +43 -0
- package/rules/no-async.js +38 -0
- package/rules/no-trailing-spaces.js +32 -0
- package/rules/one-space-after-operator.js +69 -0
- package/rules/regex-in-constructor.js +28 -0
- package/rules/ternary-parenthesis.js +29 -0
- package/rules/two-lines-between-class-members.js +45 -0
- package/rules/variable-names.js +43 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
on:
|
|
2
|
+
push:
|
|
3
|
+
tags:
|
|
4
|
+
- "v*.*.*"
|
|
5
|
+
|
|
6
|
+
name: Build and Release
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout code
|
|
14
|
+
uses: actions/checkout@v2
|
|
15
|
+
|
|
16
|
+
- name: Install NodeJS
|
|
17
|
+
uses: actions/setup-node@v1
|
|
18
|
+
with:
|
|
19
|
+
node-version: 16.x
|
|
20
|
+
registry-url: https://registry.npmjs.org
|
|
21
|
+
|
|
22
|
+
- name: Verify versions
|
|
23
|
+
run: node --version && npm --version && node -p process.versions.v8
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: npm install
|
|
27
|
+
|
|
28
|
+
- name: Release package
|
|
29
|
+
run: npm publish --ignore-scripts
|
|
30
|
+
env:
|
|
31
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/README.md
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
configs: {
|
|
3
|
+
recommended: require('./recommended'),
|
|
4
|
+
},
|
|
5
|
+
rules: {
|
|
6
|
+
"jsdoc-match-params": require("./rules/jsdoc-match-params"),
|
|
7
|
+
"align-one-var": require("./rules/align-one-var"),
|
|
8
|
+
"no-trailing-spaces": require("./rules/no-trailing-spaces"),
|
|
9
|
+
"new-line-after-block": require("./rules/new-line-after-block"),
|
|
10
|
+
"constructor-variables": require("./rules/constructor-variables"),
|
|
11
|
+
"methods-naming": require("./rules/methods-naming"),
|
|
12
|
+
"regex-in-constructor": require("./rules/regex-in-constructor"),
|
|
13
|
+
"one-space-after-operator": require("./rules/one-space-after-operator"),
|
|
14
|
+
"no-async": require("./rules/no-async"),
|
|
15
|
+
"const": require("./rules/const"),
|
|
16
|
+
"two-lines-between-class-members": require("./rules/two-lines-between-class-members"),
|
|
17
|
+
"align-requires": require("./rules/align-requires"),
|
|
18
|
+
"ternary-parenthesis": require("./rules/ternary-parenthesis"),
|
|
19
|
+
"variable-names": require("./rules/variable-names"),
|
|
20
|
+
"multiline-comment-end-backslash": require("./rules/multiline-comment-end-backslash")
|
|
21
|
+
}
|
|
22
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eslint-plugin-crisp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Custom EsLint Rules for Crisp",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"author": "Crisp IM SAS",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"doctrine": "3.0.0",
|
|
10
|
+
"eslint": "8.45.0",
|
|
11
|
+
"eslint-plugin-jsdoc": "40.3.4"
|
|
12
|
+
}
|
|
13
|
+
}
|
package/recommended.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
"env": {
|
|
3
|
+
"es6": true,
|
|
4
|
+
"node": true
|
|
5
|
+
},
|
|
6
|
+
"plugins": [
|
|
7
|
+
"eslint-plugin-jsdoc"
|
|
8
|
+
],
|
|
9
|
+
"rules": {
|
|
10
|
+
"indent": "off",
|
|
11
|
+
"linebreak-style": ["error", "unix"],
|
|
12
|
+
"quotes": ["error", "double", { "avoidEscape": true, "allowTemplateLiterals": true }],
|
|
13
|
+
"semi": ["error", "always"],
|
|
14
|
+
"max-len": ["error", 80],
|
|
15
|
+
"comma-dangle": ["error", "never"],
|
|
16
|
+
"crisp/align-one-var": ["error"],
|
|
17
|
+
"crisp/multiline-comment-end-backslash": "error",
|
|
18
|
+
"crisp/const": "error",
|
|
19
|
+
"crisp/regex-in-constructor": ["error"],
|
|
20
|
+
"crisp/align-requires": "error",
|
|
21
|
+
"crisp/two-lines-between-class-members": "error",
|
|
22
|
+
"crisp/no-async": "error",
|
|
23
|
+
"crisp/methods-naming": "error",
|
|
24
|
+
"crisp/new-line-after-block": "error",
|
|
25
|
+
"crisp/one-space-after-operator": "error",
|
|
26
|
+
"crisp/no-trailing-spaces": "error",
|
|
27
|
+
"crisp/ternary-parenthesis": "error",
|
|
28
|
+
"crisp/variable-names": "error",
|
|
29
|
+
"crisp/jsdoc-match-params": ["error", { "exceptions": ["constructor"] }],
|
|
30
|
+
|
|
31
|
+
"crisp/constructor-variables": ["error", {
|
|
32
|
+
"exceptions": ["client"]
|
|
33
|
+
}],
|
|
34
|
+
"jsdoc/require-jsdoc": ["error", {
|
|
35
|
+
"require": {
|
|
36
|
+
"FunctionDeclaration": true,
|
|
37
|
+
"MethodDefinition": true,
|
|
38
|
+
"ClassDeclaration": true,
|
|
39
|
+
"ArrowFunctionExpression": false,
|
|
40
|
+
"FunctionExpression": false
|
|
41
|
+
}
|
|
42
|
+
}]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "layout",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "enforce alignment of variables in consecutive 'one-var' statements",
|
|
6
|
+
category: "Stylistic Issues",
|
|
7
|
+
recommended: false,
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
create(context) {
|
|
11
|
+
const sourceCode = context.getSourceCode();
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
VariableDeclaration(node) {
|
|
15
|
+
if (node.declarations.length <= 1) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let assignmentOperatorColumn = null;
|
|
20
|
+
let variableNameColumn = null;
|
|
21
|
+
let lastDeclarationLine = null;
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < node.declarations.length; i++) {
|
|
24
|
+
const declaration = node.declarations[i];
|
|
25
|
+
|
|
26
|
+
// If the current declaration is on the same line as the last one,
|
|
27
|
+
// skip it
|
|
28
|
+
if (lastDeclarationLine === declaration.loc.start.line) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
lastDeclarationLine = declaration.loc.start.line;
|
|
33
|
+
|
|
34
|
+
const variableNameToken = sourceCode.getFirstToken(declaration);
|
|
35
|
+
const variableNameColumnCurrent = variableNameToken.loc.start.column;
|
|
36
|
+
|
|
37
|
+
if (variableNameColumn === null) {
|
|
38
|
+
variableNameColumn = variableNameColumnCurrent;
|
|
39
|
+
} else if (variableNameColumnCurrent !== variableNameColumn) {
|
|
40
|
+
context.report({
|
|
41
|
+
node: declaration,
|
|
42
|
+
message: "Misaligned variable declaration."
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!declaration.init) {
|
|
47
|
+
continue; // Skip if there's no assignment
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const equalsSignToken = sourceCode.getTokenBefore(declaration.init);
|
|
51
|
+
|
|
52
|
+
if (equalsSignToken.value !== "=") {
|
|
53
|
+
continue; // Skip if it's not an assignment
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const equalsSignColumn = equalsSignToken.loc.start.column;
|
|
57
|
+
|
|
58
|
+
if (assignmentOperatorColumn === null) {
|
|
59
|
+
assignmentOperatorColumn = equalsSignColumn;
|
|
60
|
+
} else if (equalsSignColumn !== assignmentOperatorColumn) {
|
|
61
|
+
context.report({
|
|
62
|
+
node: declaration,
|
|
63
|
+
message: "Misaligned assignment operator."
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "layout",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "enforce alignment of require statements",
|
|
6
|
+
category: "Stylistic Issues",
|
|
7
|
+
recommended: false,
|
|
8
|
+
},
|
|
9
|
+
fixable: null, // not auto-fixable
|
|
10
|
+
},
|
|
11
|
+
create(context) {
|
|
12
|
+
return {
|
|
13
|
+
VariableDeclaration(node) {
|
|
14
|
+
const declarations = node.declarations;
|
|
15
|
+
|
|
16
|
+
// We only check consecutive VariableDeclarator nodes with 'require' init
|
|
17
|
+
let lastColumn = null;
|
|
18
|
+
|
|
19
|
+
for (const declaration of declarations) {
|
|
20
|
+
if (declaration.init && declaration.init.callee && declaration.init.callee.name === 'require') {
|
|
21
|
+
if (lastColumn === null) {
|
|
22
|
+
lastColumn = declaration.loc.start.column;
|
|
23
|
+
} else if (declaration.loc.start.column !== lastColumn) {
|
|
24
|
+
context.report({
|
|
25
|
+
node: declaration,
|
|
26
|
+
message: "Misaligned require statement.",
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
lastColumn = null; // reset for non-require declarations
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
};
|
package/rules/const.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'suggestion',
|
|
4
|
+
docs: {
|
|
5
|
+
description: 'enforce that consts are uppercase and start with "__"',
|
|
6
|
+
category: 'Stylistic Issues',
|
|
7
|
+
recommended: false,
|
|
8
|
+
},
|
|
9
|
+
schema: [], // no options
|
|
10
|
+
},
|
|
11
|
+
create(context) {
|
|
12
|
+
return {
|
|
13
|
+
VariableDeclaration(node) {
|
|
14
|
+
if (node.kind === 'const') {
|
|
15
|
+
node.declarations.forEach((declaration) => {
|
|
16
|
+
if (declaration.id && declaration.id.name && (!declaration.id.name.startsWith('__') || declaration.id.name.toUpperCase() !== declaration.id.name)) {
|
|
17
|
+
context.report({
|
|
18
|
+
node: declaration,
|
|
19
|
+
message: 'Consts should be uppercase and start with "__"',
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "problem",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "Ensure all class properties in the constructor start with '_', except for specified exceptions",
|
|
6
|
+
category: "Stylistic Issues",
|
|
7
|
+
},
|
|
8
|
+
schema: [
|
|
9
|
+
{
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {
|
|
12
|
+
exceptions: {
|
|
13
|
+
type: "array",
|
|
14
|
+
items: { type: "string" },
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
create(context) {
|
|
23
|
+
const options = context.options[0] || {};
|
|
24
|
+
const exceptions = options.exceptions || [];
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
"MethodDefinition[kind='constructor'] > FunctionExpression > BlockStatement > ExpressionStatement > AssignmentExpression": function(node) {
|
|
28
|
+
if(node.left.type === "MemberExpression" && node.left.object.type === "ThisExpression"){
|
|
29
|
+
const varName = node.left.property.name;
|
|
30
|
+
if (!varName.startsWith("_") && !exceptions.includes(varName)) {
|
|
31
|
+
context.report({
|
|
32
|
+
node,
|
|
33
|
+
message: "Class properties in the constructor should start with '_', except for specified exceptions",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// File: eslint-plugin-custom/private-public-methods.js
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
meta: {
|
|
5
|
+
type: "problem",
|
|
6
|
+
docs: {
|
|
7
|
+
description: "Check if a method with JSDoc is properly private, protected or public",
|
|
8
|
+
category: "Possible Errors",
|
|
9
|
+
recommended: true
|
|
10
|
+
},
|
|
11
|
+
schema: [] // This rule does not take any options.
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
create: function(context) {
|
|
15
|
+
return {
|
|
16
|
+
MethodDefinition: function(node) {
|
|
17
|
+
const commentsBefore = context.getCommentsBefore(node);
|
|
18
|
+
|
|
19
|
+
const jsDocComment = commentsBefore.find(comment =>
|
|
20
|
+
comment.type === "Block" && comment.value.startsWith("*")
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
if (jsDocComment) {
|
|
24
|
+
const isPrivate = node.key.name.startsWith("__");
|
|
25
|
+
const isProtected = node.key.name.startsWith("_") && !node.key.name.startsWith("__");
|
|
26
|
+
const isPublic = jsDocComment.value.includes("@public");
|
|
27
|
+
const isProtectedJsDoc = jsDocComment.value.includes("@protected");
|
|
28
|
+
const isPrivateJsDoc = jsDocComment.value.includes("@private");
|
|
29
|
+
|
|
30
|
+
if (isPrivate && isPublic) {
|
|
31
|
+
context.report({
|
|
32
|
+
node: node,
|
|
33
|
+
message: "Methods starting with '__' should not be marked with '@public'."
|
|
34
|
+
});
|
|
35
|
+
} else if (isProtected && isPublic) {
|
|
36
|
+
context.report({
|
|
37
|
+
node: node,
|
|
38
|
+
message: "Methods starting with '_' should not be marked with '@public'."
|
|
39
|
+
});
|
|
40
|
+
} else if (!isPrivate && isPrivateJsDoc) {
|
|
41
|
+
context.report({
|
|
42
|
+
node: node,
|
|
43
|
+
message: "Methods not starting with '__' should not be marked with '@private'."
|
|
44
|
+
});
|
|
45
|
+
} else if (!isProtected && isProtectedJsDoc) {
|
|
46
|
+
context.report({
|
|
47
|
+
node: node,
|
|
48
|
+
message: "Methods not starting with '_' should not be marked with '@protected'."
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// file: eslint-plugin-crisp/lib/rules/multiline-comment-end-backslash.js
|
|
2
|
+
module.exports = {
|
|
3
|
+
meta: {
|
|
4
|
+
type: "layout",
|
|
5
|
+
docs: {
|
|
6
|
+
description: "enforce that multiline comments should end with a backslash, except for JSDoc comments",
|
|
7
|
+
category: "Stylistic Issues",
|
|
8
|
+
recommended: false,
|
|
9
|
+
},
|
|
10
|
+
fixable: null, // Not auto-fixable
|
|
11
|
+
},
|
|
12
|
+
create(context) {
|
|
13
|
+
return {
|
|
14
|
+
Program() {
|
|
15
|
+
const sourceCode = context.getSourceCode();
|
|
16
|
+
const comments = sourceCode.getAllComments();
|
|
17
|
+
|
|
18
|
+
comments.forEach(comment => {
|
|
19
|
+
if (
|
|
20
|
+
comment.type === "Block" &&
|
|
21
|
+
!comment.value.trim().endsWith('\\') &&
|
|
22
|
+
!comment.value.trim().startsWith("*")
|
|
23
|
+
) {
|
|
24
|
+
context.report({
|
|
25
|
+
node: comment,
|
|
26
|
+
message: "Multiline comments should end with a backslash '\\', unless they are JSDoc comments.",
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "layout",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "enforce a newline after control flow statements",
|
|
6
|
+
category: "Stylistic Issues",
|
|
7
|
+
recommended: false,
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
create(context) {
|
|
11
|
+
return {
|
|
12
|
+
IfStatement: checkForNewlineAfter,
|
|
13
|
+
ForStatement: checkForNewlineAfter,
|
|
14
|
+
WhileStatement: checkForNewlineAfter,
|
|
15
|
+
SwitchStatement: checkForNewlineAfter,
|
|
16
|
+
BreakStatement: checkForNewlineAfter,
|
|
17
|
+
ContinueStatement: checkForNewlineAfter,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function checkForNewlineAfter(node) {
|
|
21
|
+
const sourceCode = context.getSourceCode();
|
|
22
|
+
const tokenAfter = sourceCode.getTokenAfter(node, {includeComments: true});
|
|
23
|
+
|
|
24
|
+
if (!tokenAfter || tokenAfter.value === "}") {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const lineDifference = tokenAfter.loc.start.line - node.loc.end.line;
|
|
29
|
+
|
|
30
|
+
if (lineDifference < 2) {
|
|
31
|
+
context.report({
|
|
32
|
+
node,
|
|
33
|
+
message: "Expected a newline after control flow statement.",
|
|
34
|
+
});
|
|
35
|
+
} else if (lineDifference > 2) {
|
|
36
|
+
context.report({
|
|
37
|
+
node,
|
|
38
|
+
message: "Expected exactly one newline after control flow statement, but found more.",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
create: function(context) {
|
|
3
|
+
return {
|
|
4
|
+
FunctionDeclaration: function(node) {
|
|
5
|
+
if (node.async) {
|
|
6
|
+
context.report({
|
|
7
|
+
node: node,
|
|
8
|
+
message: "async/await is not allowed, use Promises instead.",
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
ArrowFunctionExpression: function(node) {
|
|
13
|
+
if (node.async) {
|
|
14
|
+
context.report({
|
|
15
|
+
node: node,
|
|
16
|
+
message: "async/await is not allowed, use Promises instead.",
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
FunctionExpression: function(node) {
|
|
21
|
+
if (node.async) {
|
|
22
|
+
context.report({
|
|
23
|
+
node: node,
|
|
24
|
+
message: "async/await is not allowed, use Promises instead.",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
MethodDefinition: function(node) {
|
|
29
|
+
if (node.value && node.value.async) {
|
|
30
|
+
context.report({
|
|
31
|
+
node: node,
|
|
32
|
+
message: "async/await is not allowed, use Promises instead.",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// file: eslint-plugin-crisp/lib/rules/no-trailing-spaces.js
|
|
2
|
+
module.exports = {
|
|
3
|
+
meta: {
|
|
4
|
+
type: "layout",
|
|
5
|
+
docs: {
|
|
6
|
+
description: "disallow trailing whitespace at the end of lines",
|
|
7
|
+
category: "Stylistic Issues",
|
|
8
|
+
recommended: true,
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
create(context) {
|
|
12
|
+
const sourceCode = context.getSourceCode();
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
Program() {
|
|
16
|
+
const lines = sourceCode.lines;
|
|
17
|
+
|
|
18
|
+
lines.forEach((line, index) => {
|
|
19
|
+
if (/\s+$/u.test(line)) {
|
|
20
|
+
context.report({
|
|
21
|
+
loc: {
|
|
22
|
+
start: { line: index + 1, column: line.length },
|
|
23
|
+
end: { line: index + 1, column: line.length }
|
|
24
|
+
},
|
|
25
|
+
message: "Trailing spaces not allowed."
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "layout",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "enforce only one space after = and : assignment",
|
|
6
|
+
category: "Stylistic Issues",
|
|
7
|
+
recommended: false,
|
|
8
|
+
},
|
|
9
|
+
fixable: "whitespace", // or "code" or "whitespace"
|
|
10
|
+
schema: [], // no options
|
|
11
|
+
},
|
|
12
|
+
create(context) {
|
|
13
|
+
return {
|
|
14
|
+
AssignmentExpression(node) {
|
|
15
|
+
const sourceCode = context.getSourceCode();
|
|
16
|
+
const operator = sourceCode.getTokenBefore(node.right);
|
|
17
|
+
|
|
18
|
+
if (sourceCode.getTokenAfter(operator).loc.start.column - operator.loc.end.column > 1) {
|
|
19
|
+
context.report({
|
|
20
|
+
node: operator,
|
|
21
|
+
message: "There should be exactly one space after '=' operator",
|
|
22
|
+
fix(fixer) {
|
|
23
|
+
return fixer.replaceTextRange(
|
|
24
|
+
[operator.range[1], sourceCode.getTokenAfter(operator).range[0]],
|
|
25
|
+
" "
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
VariableDeclarator(node) {
|
|
32
|
+
if (node.init) {
|
|
33
|
+
const sourceCode = context.getSourceCode();
|
|
34
|
+
const operator = sourceCode.getTokenBefore(node.init);
|
|
35
|
+
|
|
36
|
+
if (sourceCode.getTokenAfter(operator).loc.start.column - operator.loc.end.column > 1) {
|
|
37
|
+
context.report({
|
|
38
|
+
node: operator,
|
|
39
|
+
message: "There should be exactly one space after '=' operator",
|
|
40
|
+
fix(fixer) {
|
|
41
|
+
return fixer.replaceTextRange(
|
|
42
|
+
[operator.range[1], sourceCode.getTokenAfter(operator).range[0]],
|
|
43
|
+
" "
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
Property(node) {
|
|
51
|
+
const sourceCode = context.getSourceCode();
|
|
52
|
+
const operator = sourceCode.getTokenBefore(node.value);
|
|
53
|
+
|
|
54
|
+
if (sourceCode.getTokenAfter(operator).loc.start.column - operator.loc.end.column > 1) {
|
|
55
|
+
context.report({
|
|
56
|
+
node: operator,
|
|
57
|
+
message: "There should be exactly one space after ':'' operator",
|
|
58
|
+
fix(fixer) {
|
|
59
|
+
return fixer.replaceTextRange(
|
|
60
|
+
[operator.range[1], sourceCode.getTokenAfter(operator).range[0]],
|
|
61
|
+
" "
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// file: eslint-plugin-crisp/lib/rules/regex-in-constructor.js
|
|
2
|
+
module.exports = {
|
|
3
|
+
meta: {
|
|
4
|
+
type: "suggestion",
|
|
5
|
+
docs: {
|
|
6
|
+
description: "Enforce regex definitions in the constructor",
|
|
7
|
+
category: "Best Practices",
|
|
8
|
+
recommended: false,
|
|
9
|
+
},
|
|
10
|
+
fixable: null, // This rule is not auto-fixable
|
|
11
|
+
},
|
|
12
|
+
create(context) {
|
|
13
|
+
return {
|
|
14
|
+
'MethodDefinition[kind!="constructor"] > FunctionExpression > BlockStatement': function(node) {
|
|
15
|
+
const regexes = context.getSourceCode().getTokens(node).filter(token => token.type === "RegularExpression");
|
|
16
|
+
|
|
17
|
+
if (regexes.length > 0) {
|
|
18
|
+
context.report({
|
|
19
|
+
node,
|
|
20
|
+
message: "Regular expressions should be defined in the constructor."
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "layout",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "enforce parentheses around the condition in ternary expressions, if there is an operator in the condition",
|
|
6
|
+
category: "Stylistic Issues",
|
|
7
|
+
recommended: false,
|
|
8
|
+
},
|
|
9
|
+
schema: [], // no options
|
|
10
|
+
},
|
|
11
|
+
create(context) {
|
|
12
|
+
return {
|
|
13
|
+
ConditionalExpression(node) {
|
|
14
|
+
if (node.test.type === "BinaryExpression" || node.test.type === "LogicalExpression") {
|
|
15
|
+
const sourceCode = context.getSourceCode();
|
|
16
|
+
const beforeOperatorToken = sourceCode.getTokenBefore(node.test);
|
|
17
|
+
const afterOperatorToken = sourceCode.getTokenAfter(node.test);
|
|
18
|
+
|
|
19
|
+
if (beforeOperatorToken.value !== "(" || afterOperatorToken.value !== ")") {
|
|
20
|
+
context.report({
|
|
21
|
+
node,
|
|
22
|
+
message: "The condition in ternary expressions with an operator should be wrapped in parentheses",
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// file: eslint-plugin-crisp/lib/rules/two-lines-between-class-members.js
|
|
2
|
+
module.exports = {
|
|
3
|
+
meta: {
|
|
4
|
+
type: "layout",
|
|
5
|
+
docs: {
|
|
6
|
+
description: "enforce two line breaks between class members",
|
|
7
|
+
category: "Stylistic Issues",
|
|
8
|
+
recommended: false,
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
create(context) {
|
|
12
|
+
return {
|
|
13
|
+
MethodDefinition(node) {
|
|
14
|
+
const sourceCode = context.getSourceCode();
|
|
15
|
+
const lines = sourceCode.lines;
|
|
16
|
+
|
|
17
|
+
const parent = node.parent;
|
|
18
|
+
|
|
19
|
+
// Ensure we are in a class and not the first method
|
|
20
|
+
if (parent && parent.type === "ClassBody" && parent.body[0] !== node) {
|
|
21
|
+
const methodIndex = parent.body.indexOf(node);
|
|
22
|
+
const previousMethod = parent.body[methodIndex - 1];
|
|
23
|
+
const lineOfCurrentMethod = node.loc.start.line;
|
|
24
|
+
const lineOfPreviousMethodEnd = previousMethod.loc.end.line;
|
|
25
|
+
|
|
26
|
+
// Count the empty lines between the end of the previous method and the start of the current one
|
|
27
|
+
let emptyLineCount = 0;
|
|
28
|
+
for (let i = lineOfPreviousMethodEnd; i < lineOfCurrentMethod; i++) {
|
|
29
|
+
if (lines[i].trim() === "" && !lines[i].includes("*")) {
|
|
30
|
+
emptyLineCount++;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Report an error if there are not exactly two empty lines
|
|
35
|
+
if (emptyLineCount !== 2) {
|
|
36
|
+
context.report({
|
|
37
|
+
node,
|
|
38
|
+
message: "Expected exactly two line breaks between class methods.",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "suggestion",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "enforce that variables defined within a method start with '_', except for parameters",
|
|
6
|
+
category: "Stylistic Issues",
|
|
7
|
+
recommended: false,
|
|
8
|
+
},
|
|
9
|
+
schema: [], // no options
|
|
10
|
+
},
|
|
11
|
+
create(context) {
|
|
12
|
+
function checkDeclaration(node, body) {
|
|
13
|
+
body.body.forEach((statement) => {
|
|
14
|
+
if (statement.type === "VariableDeclaration") {
|
|
15
|
+
statement.declarations.forEach((declaration) => {
|
|
16
|
+
if (declaration.id.name && !declaration.id.name.startsWith("_")) {
|
|
17
|
+
context.report({
|
|
18
|
+
node: declaration,
|
|
19
|
+
message: "Variables defined within a method should start with '_'",
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
MethodDefinition(node) {
|
|
29
|
+
checkDeclaration(node, node.value.body);
|
|
30
|
+
},
|
|
31
|
+
ArrowFunctionExpression(node) {
|
|
32
|
+
if (node.body.type === "BlockStatement") {
|
|
33
|
+
checkDeclaration(node, node.body);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
FunctionExpression(node) {
|
|
37
|
+
if (node.body.type === "BlockStatement") {
|
|
38
|
+
checkDeclaration(node, node.body);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|