eslint-plugin-crisp 1.4.3 → 1.4.6

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.4.3",
3
+ "version": "1.4.6",
4
4
  "description": "Custom ESLint Rules for Crisp",
5
5
  "author": "Crisp IM SAS",
6
6
  "main": "index.js",
@@ -1,3 +1,60 @@
1
+ const COMMENT_HEADER_FORMAT = (section) => {
2
+ return `/**************************************************************************\n * ${section.toUpperCase()}\n ***************************************************************************/`;
3
+ };
4
+
5
+ function isTypeComment(comment) {
6
+ if (comment.type !== "Block" || !comment.value.startsWith("*")) {
7
+ return false;
8
+ }
9
+
10
+ const content = comment.value;
11
+
12
+ return content.includes("@import") || content.includes("@typedef");
13
+ }
14
+
15
+ function isUppercaseConstant(declaration) {
16
+ return (
17
+ declaration &&
18
+ declaration.id &&
19
+ declaration.id.name &&
20
+ declaration.id.name === declaration.id.name.toUpperCase()
21
+ );
22
+ }
23
+
24
+ function isRegex(node) {
25
+ if (node.type !== "VariableDeclaration") {
26
+ return false;
27
+ }
28
+
29
+ for (const declaration of node.declarations) {
30
+ if (declaration.init.type === "Literal" && declaration.init.regex != null) {
31
+ return true;
32
+ }
33
+
34
+ if (
35
+ declaration.init.type === "NewExpression" &&
36
+ declaration.init.callee.name === "RegExp"
37
+ ) {
38
+ return true;
39
+ }
40
+ }
41
+
42
+ return false;
43
+ }
44
+
45
+ // Checks if a variable declaration is an import or require
46
+ function isImportOrRequire(declaration) {
47
+ return (
48
+ declaration.init &&
49
+ declaration.init.type === "CallExpression" &&
50
+ (
51
+ (declaration.init.callee.name && declaration.init.callee.name === "require") ||
52
+ (declaration.init.callee.type && declaration.init.callee.type === "Import") ||
53
+ (declaration.init.callee.object && (declaration.init.callee.object || {}).type === "MetaProperty" && declaration.init.callee.object.meta.type === "Identifier" && declaration.init.callee.object.meta.name === "import")
54
+ )
55
+ );
56
+ }
57
+
1
58
  export default {
2
59
  meta: {
3
60
  type: "suggestion",
@@ -6,13 +63,12 @@ export default {
6
63
  category: "Best Practices",
7
64
  recommended: false,
8
65
  },
9
- fixable: null, // This rule is not auto-fixable
66
+ fixable: null,
10
67
  },
11
68
 
12
69
  create(context) {
13
70
  const filename = context.getFilename();
14
71
 
15
- // Only apply this rule to .js and .ts files
16
72
  if (!filename.endsWith(".js") && !filename.endsWith(".ts")) {
17
73
  return {};
18
74
  }
@@ -20,63 +76,37 @@ export default {
20
76
  let lastNode = null;
21
77
  let groupStart = null;
22
78
 
23
- // Cache all block comments once at start (sorted by position for fast lookup)
24
79
  const sourceCode = context.sourceCode || context.getSourceCode();
25
80
  const allBlockComments = sourceCode.getAllComments()
26
81
  .filter(c => c.type === "Block")
27
82
  .sort((a, b) => a.range[0] - b.range[0]);
28
83
 
29
- // This function formats the section string into a comment block header
30
- const COMMENT_HEADER_FORMAT = (section) => {
31
- return `/**************************************************************************\n * ${section.toUpperCase()}\n ***************************************************************************/`;
32
- };
84
+ const typeComments = allBlockComments.filter(c => isTypeComment(c));
33
85
 
34
- function isUppercaseConstant(declaration) {
35
- return (
36
- declaration &&
37
- declaration.id &&
38
- declaration.id.name &&
39
- declaration.id.name === declaration.id.name.toUpperCase()
40
- );
41
- }
86
+ function checkTypeCommentsGroup() {
87
+ if (typeComments.length === 0) {
88
+ return;
89
+ }
42
90
 
43
- function isRegex(node) {
44
- // Check if the node is a variable declaration
45
- if (node.type !== "VariableDeclaration") {
46
- return false;
47
- };
48
-
49
- // For each of the declarations in the variable declaration
50
- for (const declaration of node.declarations) {
51
- // If the initializer is a regex literal, return true
52
- if (declaration.init.type === "Literal" && declaration.init.regex != null) {
53
- return true;
54
- }
91
+ const firstTypeComment = typeComments[0];
92
+ const typeCommentStart = firstTypeComment.range[0];
93
+
94
+ let comment = null;
55
95
 
56
- // If the initializer is a new RegExp expression, return true
57
- if (
58
- declaration.init.type === "NewExpression" &&
59
- declaration.init.callee.name === "RegExp"
60
- ) {
61
- return true;
96
+ for (let i = allBlockComments.length - 1; i >= 0; i--) {
97
+ if (allBlockComments[i].range[1] < typeCommentStart) {
98
+ comment = allBlockComments[i];
99
+
100
+ break;
62
101
  }
63
102
  }
64
103
 
65
- // If none of the declarations are regexes, return false
66
- return false;
67
- }
68
-
69
- // Checks if a variable declaration is an import or require
70
- function isImportOrRequire(declaration) {
71
- return (
72
- declaration.init &&
73
- declaration.init.type === "CallExpression" &&
74
- (
75
- (declaration.init.callee.name && declaration.init.callee.name === "require") ||
76
- (declaration.init.callee.type && declaration.init.callee.type === "Import") ||
77
- (declaration.init.callee.object && (declaration.init.callee.object || {}).type === "MetaProperty" && declaration.init.callee.object.meta.type === "Identifier" && declaration.init.callee.object.meta.name === "import")
78
- )
79
- );
104
+ if (!comment || `/*${comment.value.trim()}*/` !== COMMENT_HEADER_FORMAT("TYPES")) {
105
+ context.report({
106
+ loc: firstTypeComment.loc,
107
+ message: "Type comments group must be preceded by a 'TYPES' comment block",
108
+ });
109
+ }
80
110
  }
81
111
 
82
112
  function checkGroup(nodeType, startNode) {
@@ -180,6 +210,9 @@ export default {
180
210
  if (groupStart) {
181
211
  checkGroup(lastNode.type, groupStart);
182
212
  }
213
+
214
+ // Check type comments group
215
+ checkTypeCommentsGroup();
183
216
  }
184
217
  };
185
218
  }
@@ -1,3 +1,51 @@
1
+ function isTypeComment(comment) {
2
+ return (
3
+ comment.type === "Block" &&
4
+ comment.value.startsWith("*") &&
5
+ (comment.value.includes("@import") || comment.value.includes("@typedef"))
6
+ );
7
+ }
8
+
9
+ function isTypesGroupComment(comment) {
10
+ return comment.type === "Line" && comment.value.trim() === "TYPES";
11
+ }
12
+
13
+ function isGroupComment(comment) {
14
+ return (
15
+ comment.type === "Line" &&
16
+ (
17
+ comment.value.trim().startsWith("PROJECT:") ||
18
+ comment.value.trim().startsWith("NPM") ||
19
+ comment.value.trim().startsWith("TYPES")
20
+ )
21
+ );
22
+ }
23
+
24
+ function isIgnoreComment(comment) {
25
+ return (
26
+ comment.type === "Line" &&
27
+ comment.value.trim().startsWith("@ts-ignore")
28
+ );
29
+ }
30
+
31
+ function getDirectoryName(filePath) {
32
+ const pathParts = filePath.split("/");
33
+
34
+ return pathParts[pathParts.length - 2].toUpperCase();
35
+ }
36
+
37
+ function generateExpectedComment(group) {
38
+ if (group === "NPM") {
39
+ return group;
40
+ }
41
+
42
+ if (group === "SRC") {
43
+ group = "MAIN";
44
+ }
45
+
46
+ return `PROJECT: ${group}`;
47
+ }
48
+
1
49
  export default {
2
50
  meta: {
3
51
  type: "suggestion",
@@ -21,18 +69,12 @@ export default {
21
69
  const customGroups = context.options[0] || {}; // Get custom groups from options
22
70
 
23
71
  // 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
- );
72
+ const compiledCustomGroups = Object.entries(customGroups).map(([regexStr, group]) => {
73
+ return [new RegExp(regexStr), group];
74
+ });
27
75
 
28
76
  let currentGroupComment = null;
29
77
 
30
- // Extract the directory name from the file path
31
- function getDirectoryName(filePath) {
32
- const pathParts = filePath.split("/");
33
- return pathParts[pathParts.length - 2].toUpperCase(); // Directory name
34
- }
35
-
36
78
  function extractGroupFromPath(path, filePath) {
37
79
  // Check custom regexes first (using pre-compiled patterns)
38
80
  for (const [regex, group] of compiledCustomGroups) {
@@ -60,36 +102,6 @@ export default {
60
102
  return "NPM";
61
103
  }
62
104
 
63
- function generateExpectedComment(group) {
64
- if (group === "NPM") {
65
- return group;
66
- }
67
-
68
- if (group === "SRC") {
69
- group = "MAIN";
70
- }
71
-
72
- return `PROJECT: ${group}`;
73
- }
74
-
75
- function isGroupComment(comment) {
76
- return (
77
- comment.type === "Line" &&
78
- (
79
- comment.value.trim().startsWith("PROJECT:") ||
80
- comment.value.trim().startsWith("NPM")
81
- )
82
- );
83
- }
84
-
85
- function isIgnoreComment(comment) {
86
- // Skip some comments
87
- return (
88
- comment.type === "Line" &&
89
- comment.value.trim().startsWith("@ts-ignore")
90
- );
91
- }
92
-
93
105
  function updateCurrentGroupComment(node) {
94
106
  const comments = context.getSourceCode().getCommentsBefore(node);
95
107
 
@@ -127,8 +139,49 @@ export default {
127
139
  }
128
140
  }
129
141
 
142
+ const sourceCode = context.sourceCode || context.getSourceCode();
143
+ const allComments = sourceCode.getAllComments();
144
+ const typeComments = allComments.filter(c => isTypeComment(c));
145
+
146
+ function checkTypeCommentsGroup() {
147
+ const filename = context.getFilename();
148
+
149
+ // Only check for // TYPES in .vue files (block header is checked by header-comments-check rule for .js/.ts)
150
+ if (!filename.endsWith(".vue")) {
151
+ return;
152
+ }
153
+
154
+ if (typeComments.length === 0) {
155
+ return;
156
+ }
157
+
158
+ const firstTypeComment = typeComments[0];
159
+ const typeCommentStart = firstTypeComment.range[0];
160
+
161
+ let comment = null;
162
+
163
+ for (let i = allComments.length - 1; i >= 0; i--) {
164
+ if (allComments[i].range[1] < typeCommentStart) {
165
+ comment = allComments[i];
166
+
167
+ break;
168
+ }
169
+ }
170
+
171
+ if (!comment || !isTypesGroupComment(comment)) {
172
+ context.report({
173
+ loc: firstTypeComment.loc,
174
+ message: "Type comments group must be preceded by a '// TYPES' comment",
175
+ });
176
+ }
177
+ }
178
+
130
179
  return {
131
180
  ImportDeclaration: checkImportGroup,
181
+
182
+ "Program:exit": () => {
183
+ checkTypeCommentsGroup();
184
+ }
132
185
  };
133
186
  }
134
187
  };