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 CHANGED
@@ -1,22 +1,29 @@
1
1
  module.exports = {
2
2
  configs: {
3
- recommended: require('./recommended'),
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
- "no-trailing-spaces": require("./rules/no-trailing-spaces"),
9
- "new-line-after-block": require("./rules/new-line-after-block"),
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
- "regex-in-constructor": require("./rules/regex-in-constructor"),
13
- "one-space-after-operator": require("./rules/one-space-after-operator"),
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
- "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
+ "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
- "variable-names": require("./rules/variable-names"),
20
- "multiline-comment-end-backslash": require("./rules/multiline-comment-end-backslash")
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.2",
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": "40.3.0"
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
- "env": {
3
- "es6": true,
4
- "node": true
2
+ env: {
3
+ es6: true,
4
+ node: true
5
5
  },
6
- "plugins": [
7
- "eslint-plugin-jsdoc"
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
- "rules": {
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
- "crisp/align-one-var": ["error"],
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": ["error"],
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
+ };
@@ -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
 
@@ -1,4 +1,3 @@
1
- // file: eslint-plugin-crisp/lib/rules/multiline-comment-end-backslash.js
2
1
  module.exports = {
3
2
  meta: {
4
3
  type: "layout",
@@ -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
+ };
@@ -1,4 +1,3 @@
1
- // file: eslint-plugin-crisp/lib/rules/no-trailing-spaces.js
2
1
  module.exports = {
3
2
  meta: {
4
3
  type: "layout",
@@ -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
+ }
@@ -1,4 +1,3 @@
1
- // file: eslint-plugin-crisp/lib/rules/regex-in-constructor.js
2
1
  module.exports = {
3
2
  meta: {
4
3
  type: "suggestion",
@@ -24,5 +23,3 @@ module.exports = {
24
23
  };
25
24
  },
26
25
  };
27
-
28
-
@@ -1,4 +1,3 @@
1
- // file: eslint-plugin-crisp/lib/rules/two-lines-between-class-members.js
2
1
  module.exports = {
3
2
  meta: {
4
3
  type: "layout",
@@ -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
- }], // options schema updated
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, body) {
26
- body.body.forEach((statement) => {
27
- if (statement.type === "VariableDeclaration") {
28
- statement.declarations.forEach((declaration) => {
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 '_' ({${declaration.id.name}})`,
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, node.value.body);
119
+ checkDeclaration(node.value.body);
43
120
  },
121
+
44
122
  ArrowFunctionExpression(node) {
45
123
  if (node.body.type === "BlockStatement") {
46
- checkDeclaration(node, node.body);
124
+ checkDeclaration(node.body);
47
125
  }
48
126
  },
127
+
49
128
  FunctionExpression(node) {
50
129
  if (node.body.type === "BlockStatement") {
51
- checkDeclaration(node, node.body);
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
- };