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.
@@ -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
@@ -0,0 +1,2 @@
1
+ # crisp-plugin-eslint
2
+ Custom EsLint Rules for Crisp
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
+ };