eslint-plugin-crisp 1.3.6 → 1.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-crisp",
3
- "version": "1.3.6",
3
+ "version": "1.4.0",
4
4
  "description": "Custom ESLint Rules for Crisp",
5
5
  "author": "Crisp IM SAS",
6
6
  "main": "index.js",
package/recommended.js CHANGED
@@ -94,7 +94,7 @@ export default function configRecommended(pluginCrisp) {
94
94
  "linebreak-style": ["error", "unix"],
95
95
  "max-len": ["error", {
96
96
  "code": 80,
97
- "ignorePattern": "^\\*\\* \@import"
97
+ "ignorePattern": "^\\/\\*\\* \@import"
98
98
  }],
99
99
  "no-console": "warn",
100
100
  "no-debugger": "warn",
@@ -11,7 +11,7 @@ export default {
11
11
  },
12
12
 
13
13
  create(context) {
14
- const sourceCode = context.getSourceCode();
14
+ const sourceCode = context.sourceCode || context.getSourceCode();
15
15
  const processedComments = new Set();
16
16
 
17
17
  function checkNode(node) {
@@ -1,3 +1,7 @@
1
+ // Pre-compiled patterns for exclusion checks (avoid creating regex in loop)
2
+ const OBJECT_METHODS_PATTERN = /Object\.(keys|values|entries)\(/;
3
+ const ARRAY_METHODS_PATTERN = /Array\.(from)\(/;
4
+
1
5
  export default {
2
6
  meta: {
3
7
  type: "problem",
@@ -10,7 +14,7 @@ export default {
10
14
  },
11
15
 
12
16
  create(context) {
13
- const sourceCode = context.getSourceCode();
17
+ const sourceCode = context.sourceCode || context.getSourceCode();
14
18
 
15
19
  const patterns = [
16
20
  // Search for `foo && foo.` or `foo && foo[`
@@ -44,8 +48,8 @@ export default {
44
48
 
45
49
  if (
46
50
  index === 1 &&
47
- (/Object\.(keys|values|entries)\(/.test(match[1]) ||
48
- /Array\.(from)\(/.test(match[1]))
51
+ (OBJECT_METHODS_PATTERN.test(match[1]) ||
52
+ ARRAY_METHODS_PATTERN.test(match[1]))
49
53
  ) {
50
54
  // Ignore `Object.keys(foo || {})` (and others) as it cannot be \
51
55
  // safely transformed to optional chaining
@@ -1,4 +1,3 @@
1
- import fs from "fs";
2
1
  import path from "path";
3
2
 
4
3
  export default {
@@ -29,10 +28,9 @@ export default {
29
28
  Program(node) {
30
29
  const options = context.options[0] || {};
31
30
  const fileName = context.getFilename();
31
+ const sourceCode = context.sourceCode || context.getSourceCode();
32
32
 
33
- let fileContent = fs.readFileSync(path.resolve(fileName), "utf8");
34
-
35
- fileContent = fileContent.trim();
33
+ let fileContent = sourceCode.getText().trim();
36
34
 
37
35
  // Determine the file extension
38
36
  const fileExtension = path.extname(fileName);
@@ -20,6 +20,12 @@ export default {
20
20
  let lastNode = null;
21
21
  let groupStart = null;
22
22
 
23
+ // Cache all block comments once at start (sorted by position for fast lookup)
24
+ const sourceCode = context.sourceCode || context.getSourceCode();
25
+ const allBlockComments = sourceCode.getAllComments()
26
+ .filter(c => c.type === "Block")
27
+ .sort((a, b) => a.range[0] - b.range[0]);
28
+
23
29
  // This function formats the section string into a comment block header
24
30
  const COMMENT_HEADER_FORMAT = (section) => {
25
31
  return `/**************************************************************************\n * ${section.toUpperCase()}\n ***************************************************************************/`;
@@ -79,9 +85,17 @@ export default {
79
85
  return;
80
86
  }
81
87
 
82
- // Find the nearest Block Comment before the startNode
83
- const tokens = context.getSourceCode().getTokensBefore(startNode, {includeComments: true});
84
- const comment = tokens.reverse().find(token => token.type === "Block");
88
+ // Find the nearest Block Comment before the startNode (using cached comments)
89
+ const nodeStart = startNode.range[0];
90
+ let comment = null;
91
+
92
+ for (let i = allBlockComments.length - 1; i >= 0; i--) {
93
+ if (allBlockComments[i].range[1] < nodeStart) {
94
+ comment = allBlockComments[i];
95
+
96
+ break;
97
+ }
98
+ }
85
99
 
86
100
  // Different types of nodes require different comment blocks
87
101
  switch (nodeType) {
@@ -20,6 +20,11 @@ export default {
20
20
  create(context) {
21
21
  const customGroups = context.options[0] || {}; // Get custom groups from options
22
22
 
23
+ // Pre-compile custom regexes once instead of on every import
24
+ const compiledCustomGroups = Object.entries(customGroups).map(
25
+ ([regexStr, group]) => [new RegExp(regexStr), group]
26
+ );
27
+
23
28
  let currentGroupComment = null;
24
29
 
25
30
  // Extract the directory name from the file path
@@ -29,10 +34,10 @@ export default {
29
34
  }
30
35
 
31
36
  function extractGroupFromPath(path, filePath) {
32
- // Check custom regexes first
33
- for (const regexStr in customGroups) {
34
- if (new RegExp(regexStr).test(path)) {
35
- return customGroups[regexStr];
37
+ // Check custom regexes first (using pre-compiled patterns)
38
+ for (const [regex, group] of compiledCustomGroups) {
39
+ if (regex.test(path)) {
40
+ return group;
36
41
  }
37
42
  }
38
43
 
@@ -1,3 +1,6 @@
1
+ // Pre-compiled regex for access modifier check
2
+ const ACCESS_MODIFIER_PATTERN = /@(public|protected|private)/;
3
+
1
4
  export default {
2
5
  meta: {
3
6
  type: "problem",
@@ -9,14 +12,16 @@ export default {
9
12
  },
10
13
 
11
14
  create: function(context) {
12
- function checkNodeForJSDoc(node, context) {
13
- const comments = context.getSourceCode().getCommentsBefore(node);
15
+ const sourceCode = context.sourceCode || context.getSourceCode();
16
+
17
+ function checkNodeForJSDoc(node) {
18
+ const comments = sourceCode.getCommentsBefore(node);
14
19
 
15
20
  if (comments.length > 0) {
16
21
  const lastComment = comments[comments.length - 1];
17
22
 
18
23
  if (lastComment.type === "Block" && lastComment.value.startsWith("*")) {
19
- if (!/@(public|protected|private)/.test(lastComment.value)) {
24
+ if (!ACCESS_MODIFIER_PATTERN.test(lastComment.value)) {
20
25
  context.report({
21
26
  node,
22
27
  message: "JSDoc comment should contain @public, @protected or @private",
@@ -33,20 +38,20 @@ export default {
33
38
  Property(node) {
34
39
  if (node.value.type === "FunctionExpression" ||
35
40
  node.value.type === "ArrowFunctionExpression") {
36
- checkNodeForJSDoc(node, context);
41
+ checkNodeForJSDoc(node);
37
42
  }
38
43
  },
39
44
 
40
45
  MethodDefinition(node) {
41
- checkNodeForJSDoc(node, context);
46
+ checkNodeForJSDoc(node);
42
47
  },
43
48
 
44
49
  FunctionDeclaration(node) {
45
- checkNodeForJSDoc(node, context);
50
+ checkNodeForJSDoc(node);
46
51
  },
47
52
 
48
53
  FunctionExpression(node) {
49
- checkNodeForJSDoc(node, context);
54
+ checkNodeForJSDoc(node);
50
55
  },
51
56
  };
52
57
  }
@@ -1,15 +1,19 @@
1
1
  import doctrine from "doctrine";
2
2
 
3
+ // Pre-compiled regex for section comment matching
4
+ const SECTION_COMMENT_PATTERN = /--> (.*) <--/;
5
+
3
6
  export default {
4
7
  create(context) {
5
8
  let methodOrder = []; // Keep track of method order
9
+ const sourceCode = context.sourceCode || context.getSourceCode();
6
10
 
7
11
  return {
8
12
  MethodDefinition(node) {
9
- const commentsBefore = context.getSourceCode().getCommentsBefore(node);
13
+ const commentsBefore = sourceCode.getCommentsBefore(node);
10
14
 
11
15
  // Check if there is a comment matching `--> {VALUE} <--` before the node
12
- const nodeComment = commentsBefore.find(comment => comment.type === "Line" && /--> (.*) <--/.test(comment.value));
16
+ const nodeComment = commentsBefore.find(comment => comment.type === "Line" && SECTION_COMMENT_PATTERN.test(comment.value));
13
17
 
14
18
  // Reset methodOrder if a matching comment was found
15
19
  if (nodeComment) {
@@ -1,3 +1,6 @@
1
+ // Pre-compiled regex for constant naming check
2
+ const UPPER_SNAKE_CASE_PATTERN = /^[A-Z][A-Z0-9_]*$/;
3
+
1
4
  export default {
2
5
  meta: {
3
6
  type: "suggestion",
@@ -26,7 +29,7 @@ export default {
26
29
  }
27
30
 
28
31
  // Allow UPPER_SNAKE_CASE for constants
29
- if (isConstant && cleanName === cleanName.toUpperCase() && /^[A-Z][A-Z0-9_]*$/.test(cleanName)) {
32
+ if (isConstant && cleanName === cleanName.toUpperCase() && UPPER_SNAKE_CASE_PATTERN.test(cleanName)) {
30
33
  return false;
31
34
  }
32
35
 
@@ -23,7 +23,7 @@ export default {
23
23
  ]
24
24
  },
25
25
  create(context) {
26
- const sourceCode = context.getSourceCode();
26
+ const sourceCode = context.sourceCode || context.getSourceCode();
27
27
  const config = context.options[0] || {};
28
28
  const checkColon = config.checkColon !== false;
29
29
 
@@ -1,5 +1,8 @@
1
1
  import utils from "eslint-plugin-vue/dist/utils/index.js";
2
2
 
3
+ // Pre-compiled regex for snake_case ref validation
4
+ const SNAKE_CASE_REF_PATTERN = /^[a-z]+(_[a-z]+)*$/;
5
+
3
6
  export default {
4
7
  meta: {
5
8
  type: "layout",
@@ -18,7 +21,7 @@ export default {
18
21
  if (node.value && node.value.type === "VLiteral") {
19
22
  const refValue = node.value.value;
20
23
 
21
- if (refValue && !/^[a-z]+(_[a-z]+)*$/.test(refValue)) {
24
+ if (refValue && !SNAKE_CASE_REF_PATTERN.test(refValue)) {
22
25
  context.report({
23
26
  node,
24
27
  message: "Ref attribute \"{{refValue}}\" should be snake-cased.",