eslint-plugin-crisp 1.4.8 → 1.4.10

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/README.md CHANGED
@@ -115,6 +115,7 @@ Each item has emojis denoting:
115
115
  | Name | Description | 🟠 | 🟢 | 🟣 |
116
116
  | :- | :- | :- | :- | :- |
117
117
  | [crisp/align-comments](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/align-comments.js) | Enforces alignment of comments compared to the previous line (the `indent` rule doesn't check this case) | 🟠 | 🟢 | 🟣 |
118
+ | [crisp/consecutive-line-comments](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/consecutive-line-comments.js) | Enforces consecutive single-line comments to form a single multi-line continuation block | 🟠 | 🟢 | 🟣 |
118
119
  | [crisp/align-consecutive-class-assignements](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/align-consecutive-class-assignements.js) | Enforces alignment of consecutive assignment statements in a class constructor | 🟠 |
119
120
  | [crisp/align-one-var](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/align-one-var.js) | Enforces alignment of variables in 'one-var' statements | 🟠 |
120
121
  | [crisp/align-requires](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/align-requires.js) | Enforces alignment of require statements | 🟠 |
@@ -146,12 +147,18 @@ Each item has emojis denoting:
146
147
  | Name | Description | 🟠 | 🟢 | 🟣 |
147
148
  | :- | :- | :- | :- | :- |
148
149
  | [jsdoc/no-undefined-types](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/no-undefined-types.md) | Rule is **disabled** to allow some undefined types | 🟠 |
150
+ | [jsdoc/no-types](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/no-types.md) | Disallows types in JSDoc tags, relying on the TypeScript signature instead | | | 🟣 |
149
151
  | [jsdoc/require-description](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-description.md) | Requires all functions to have a description in their JSDoc | | 🟢 | 🟣 |
150
152
  | [jsdoc/require-param](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-param.md) | Rule is **disabled** as TS functions are self documented | | | 🟣 |
153
+ | [jsdoc/require-param-type](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-param-type.md) | Rule is **disabled** as types come from the TypeScript signature | | | 🟣 |
151
154
  | [jsdoc/require-param-description](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-param-description.md) | Rule is **disabled** as we don't write any description for `@param` tags | 🟠 | 🟢 | 🟣 |
152
- | [jsdoc/require-property-description](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-param-description.md) | Rule is **disabled** as we don't write any description for `@property` tags | 🟠 | 🟢 | 🟣 |
153
- | [jsdoc/require-param](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-returns.md) | Rule is **disabled** as TS functions are self documented | | | 🟣 |
154
- | [jsdoc/require-param](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-yields.md) | Rule is **disabled** as TS functions are self documented | | | 🟣 |
155
+ | [jsdoc/check-param-names](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-param-names.md) | Rule is **disabled** as TS functions are self documented | | | 🟣 |
156
+ | [jsdoc/require-property-description](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-property-description.md) | Rule is **disabled** as we don't write any description for `@property` tags | 🟠 | 🟢 | 🟣 |
157
+ | [jsdoc/require-returns](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-returns.md) | Rule is **disabled** as TS functions are self documented | | | 🟣 |
158
+ | [jsdoc/require-returns-type](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-returns-type.md) | Rule is **disabled** as types come from the TypeScript signature | | | 🟣 |
159
+ | [jsdoc/require-returns-check](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-returns-check.md) | Rule is **disabled** as we don't write `@returns` tags | | | 🟣 |
160
+ | [jsdoc/require-yields](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-yields.md) | Rule is **disabled** as TS functions are self documented | | | 🟣 |
161
+ | [jsdoc/require-yields-check](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-yields-check.md) | Rule is **disabled** as we don't write `@yields` tags | | | 🟣 |
155
162
  | [jsdoc/require-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-jsdoc.md) | Enforces JSDoc comments on functions and classes | 🟠 | 🟢 | 🟣 |
156
163
  | [jsdoc/sort-tags](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/sort-tags.md) | Enforces specific order for tags | 🟠 | 🟢 | 🟣 |
157
164
 
@@ -165,6 +172,8 @@ Each item has emojis denoting:
165
172
  | [crisp/jsdoc-enforce-access](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/jsdoc-enforce-access.js) | Requires one of `@public`, `@private`, or `@protected` for functions | | 🟢 |
166
173
  | [crisp/jsdoc-enforce-classdesc](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/jsdoc-enforce-classdesc.js) | Ensures JSDoc for class headers to include a non-empty `@classdesc` | 🟠 | 🟢 |
167
174
  | [crisp/jsdoc-require-description-uppercase](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/jsdoc-require-description-uppercase.js) | Requires descriptions to start with an uppercase character | 🟠 | 🟢 |
175
+ | [crisp/jsdoc-description-length](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/jsdoc-description-length.js) | Enforces a maximum length for JSDoc descriptions | 🟠 | 🟢 | 🟣 |
176
+ | [crisp/jsdoc-no-param-return](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/jsdoc-no-param-return.js) | Forbids `@param` and `@return` tags in JSDoc, relying on the TypeScript signature instead | | | 🟣 |
168
177
 
169
178
  #### General Vue rules
170
179
 
package/index.js CHANGED
@@ -6,6 +6,7 @@ import ruleAlignComments from "./rules/align-comments.js";
6
6
  import ruleAlignConsecutiveClassAssignements from "./rules/align-consecutive-class-assignements.js";
7
7
  import ruleAlignOneVar from "./rules/align-one-var.js";
8
8
  import ruleAlignRequires from "./rules/align-requires.js";
9
+ import ruleConsecutiveLineComments from "./rules/consecutive-line-comments.js";
9
10
  import ruleConst from "./rules/const.js";
10
11
  import ruleConstructorVariables from "./rules/constructor-variables.js";
11
12
  import ruleEnforceOptional from "./rules/enforce-optional.js";
@@ -18,6 +19,7 @@ import ruleJsdocCheckIndentation from "./rules/jsdoc-check-indentation.js";
18
19
  import ruleJsdocCheckOptionalParams from "./rules/jsdoc-check-optional-params.js";
19
20
  import ruleJsdocEnforceAccess from "./rules/jsdoc-enforce-access.js";
20
21
  import ruleJsdocEnforceClassdesc from "./rules/jsdoc-enforce-classdesc.js";
22
+ import ruleJsdocNoParamReturn from "./rules/jsdoc-no-param-return.js";
21
23
  import ruleJsdocRequireDescriptionUppercase from "./rules/jsdoc-require-description-uppercase.js";
22
24
  import ruleJsdocDescriptionLength from "./rules/jsdoc-description-length.js";
23
25
  import ruleMethodsNaming from "./rules/methods-naming.js";
@@ -69,6 +71,7 @@ const plugin = {
69
71
  "align-consecutive-class-assignements": ruleAlignConsecutiveClassAssignements,
70
72
  "align-one-var": ruleAlignOneVar,
71
73
  "align-requires": ruleAlignRequires,
74
+ "consecutive-line-comments": ruleConsecutiveLineComments,
72
75
  "const": ruleConst,
73
76
  "constructor-variables": ruleConstructorVariables,
74
77
  "enforce-optional": ruleEnforceOptional,
@@ -81,6 +84,7 @@ const plugin = {
81
84
  "jsdoc-check-optional-params": ruleJsdocCheckOptionalParams,
82
85
  "jsdoc-enforce-access": ruleJsdocEnforceAccess,
83
86
  "jsdoc-enforce-classdesc": ruleJsdocEnforceClassdesc,
87
+ "jsdoc-no-param-return": ruleJsdocNoParamReturn,
84
88
  "jsdoc-require-description-uppercase": ruleJsdocRequireDescriptionUppercase,
85
89
  "jsdoc-description-length": ruleJsdocDescriptionLength,
86
90
  "methods-naming": ruleMethodsNaming,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-crisp",
3
- "version": "1.4.8",
3
+ "version": "1.4.10",
4
4
  "description": "Custom ESLint Rules for Crisp",
5
5
  "author": "Crisp IM SAS",
6
6
  "main": "index.js",
package/recommended-ts.js CHANGED
@@ -151,6 +151,7 @@ export default function configRecommendedTS(pluginCrisp) {
151
151
 
152
152
  // Crisp JS rules
153
153
  "crisp/align-comments": "error",
154
+ "crisp/consecutive-line-comments": "error",
154
155
  "crisp/enforce-optional": "error",
155
156
  "crisp/header-check": "error",
156
157
  "crisp/header-comments-check": "error",
@@ -183,14 +184,21 @@ export default function configRecommendedTS(pluginCrisp) {
183
184
 
184
185
  // Crisp JSDoc rules
185
186
  "crisp/jsdoc-description-length": "error",
187
+ "crisp/jsdoc-no-param-return": "error",
186
188
 
187
189
  // General JSDoc rules
190
+ "jsdoc/no-types": "error",
188
191
  "jsdoc/require-description": "error",
189
192
  "jsdoc/require-param": "off",
193
+ "jsdoc/require-param-type": "off",
190
194
  "jsdoc/require-param-description": "off",
195
+ "jsdoc/check-param-names": "off",
191
196
  "jsdoc/require-property-description": "off",
192
197
  "jsdoc/require-returns": "off",
198
+ "jsdoc/require-returns-type": "off",
199
+ "jsdoc/require-returns-check": "off",
193
200
  "jsdoc/require-yields": "off",
201
+ "jsdoc/require-yields-check": "off",
194
202
  "jsdoc/require-jsdoc": [
195
203
  "error",
196
204
 
@@ -162,6 +162,7 @@ export default function configRecommendedVue(pluginCrisp) {
162
162
 
163
163
  // Crisp JS rules
164
164
  "crisp/align-comments": "error",
165
+ "crisp/consecutive-line-comments": "error",
165
166
  "crisp/enforce-optional": "error",
166
167
  "crisp/header-check": "error",
167
168
  "crisp/header-comments-check": "error",
@@ -247,6 +248,7 @@ export default function configRecommendedVue(pluginCrisp) {
247
248
  "crisp/jsdoc-enforce-access": "error",
248
249
  "crisp/jsdoc-enforce-classdesc": "error",
249
250
  "crisp/jsdoc-require-description-uppercase": "error",
251
+ "crisp/jsdoc-description-length": "error",
250
252
 
251
253
  // General Vue rules
252
254
  "vue/attributes-order": [
package/recommended.js CHANGED
@@ -156,6 +156,7 @@ export default function configRecommended(pluginCrisp) {
156
156
 
157
157
  // Crisp JS rules
158
158
  "crisp/align-comments": "error",
159
+ "crisp/consecutive-line-comments": "error",
159
160
  "crisp/align-consecutive-class-assignements": "error",
160
161
  "crisp/align-one-var": "error",
161
162
  "crisp/align-requires": "error",
@@ -232,7 +233,8 @@ export default function configRecommended(pluginCrisp) {
232
233
  "crisp/jsdoc-align-params": "error",
233
234
  "crisp/jsdoc-check-indentation": "error",
234
235
  "crisp/jsdoc-enforce-classdesc": "error",
235
- "crisp/jsdoc-require-description-uppercase": "error"
236
+ "crisp/jsdoc-require-description-uppercase": "error",
237
+ "crisp/jsdoc-description-length": "error"
236
238
  }
237
239
  }
238
240
  ];
@@ -0,0 +1,250 @@
1
+ const DIRECTIVE_REGEX = /^(eslint|ts-|@ts|prettier-|global |globals |istanbul|c8 |v8 |jshint|jslint)/;
2
+
3
+ // A short capitalized label followed by a colon (e.g. 'Notice:', 'Important:',
4
+ // 'Source:') introduces a new logical comment, so the previous comment does not
5
+ // need to be merged into a continuation block with it. The label is kept short
6
+ // (up to ~40 chars) to avoid matching a regular sentence that contains a colon
7
+ const BOUNDARY_REGEX = /^[A-Z][^:]{0,39}:/;
8
+
9
+ function getCore(comment) {
10
+ // Strip the trailing continuation backslash and surrounding whitespace
11
+ return comment.value.replace(/\\\s*$/, "").trim();
12
+ }
13
+
14
+ function isDirective(core) {
15
+ return DIRECTIVE_REGEX.test(core);
16
+ }
17
+
18
+ function isBoundary(core) {
19
+ return BOUNDARY_REGEX.test(core);
20
+ }
21
+
22
+ function isContinuationLine(comment) {
23
+ // Continuation lines use at least three spaces after '//'
24
+ return /^ {3,}/.test(comment.value);
25
+ }
26
+
27
+ function isBoundaryComment(comment) {
28
+ // Labels such as 'Notice:' only split groups on first lines, not mid-block
29
+ // prose like '// In other words: …'
30
+ return !isContinuationLine(comment) && isBoundary(getCore(comment));
31
+ }
32
+
33
+ function endsWithContinuation(comment) {
34
+ return /\\\s*$/.test(comment.value);
35
+ }
36
+
37
+ function isBlankSeparatorLine(comment) {
38
+ // A line that only contains '\' (e.g. '// \' or '// \') is a visual separator
39
+ return (
40
+ comment.type === "Line" &&
41
+ endsWithContinuation(comment) &&
42
+ getCore(comment).length === 0
43
+ );
44
+ }
45
+
46
+ export default {
47
+ meta: {
48
+ type: "layout",
49
+ docs: {
50
+ description:
51
+ "Enforce that consecutive single-line comments are written as a " +
52
+ "single multi-line continuation block.",
53
+ category: "Stylistic Issues",
54
+ recommended: false,
55
+ },
56
+ fixable: "whitespace",
57
+ schema: [],
58
+ messages: {
59
+ format:
60
+ "Consecutive single-line comments must form a continuation block: " +
61
+ "each line ends with '\\' (except the last) and continuation lines " +
62
+ "are indented with at least three spaces after '//'.",
63
+ },
64
+ },
65
+
66
+ create(context) {
67
+ const sourceCode = context.sourceCode || context.getSourceCode();
68
+
69
+ function isStandalone(comment) {
70
+ const lineText = sourceCode.lines[comment.loc.start.line - 1] || "";
71
+ const before = lineText.slice(0, comment.loc.start.column);
72
+
73
+ return before.trim() === "";
74
+ }
75
+
76
+ function getCanonicalCommentText(comment, index, groupLength) {
77
+ const core = getCore(comment);
78
+ const isLast = index === groupLength - 1;
79
+ const suffix = isLast ? "" : " \\";
80
+
81
+ if (index === 0) {
82
+ return `// ${core}${suffix}`;
83
+ }
84
+
85
+ return `// ${core}${suffix}`;
86
+ }
87
+
88
+ function commentMatchesCanonical(comment, index, groupLength) {
89
+ const core = getCore(comment);
90
+ const isLast = index === groupLength - 1;
91
+ const line = sourceCode.lines[comment.loc.start.line - 1] || "";
92
+ const actual = line.slice(comment.loc.start.column);
93
+
94
+ if (isBlankSeparatorLine(comment) && index < groupLength - 1) {
95
+ return /^\/\/\s*\\$/.test(actual);
96
+ }
97
+
98
+ if (index === 0) {
99
+ const expected = `// ${core}${isLast ? "" : " \\"}`;
100
+
101
+ return actual === expected;
102
+ }
103
+
104
+ const match = actual.match(/^\/\/(\s+)(.*)$/);
105
+
106
+ if (!match || match[1].length < 3) {
107
+ return false;
108
+ }
109
+
110
+ let body = match[2];
111
+
112
+ if (!isLast) {
113
+ if (!/\\\s*$/.test(body)) {
114
+ return false;
115
+ }
116
+
117
+ body = body.replace(/\\\s*$/, "");
118
+ }
119
+
120
+ return body.trim() === core;
121
+ }
122
+
123
+ function checkGroup(group) {
124
+ if (group.length < 2) {
125
+ return;
126
+ }
127
+
128
+ const indent = " ".repeat(group[0].loc.start.column);
129
+
130
+ const canonicalLines = group.map((comment, index) => {
131
+ return getCanonicalCommentText(comment, index, group.length);
132
+ });
133
+
134
+ const canonical = canonicalLines.join(`\n${indent}`);
135
+
136
+ const allMatch = group.every((comment, index) => {
137
+ return commentMatchesCanonical(comment, index, group.length);
138
+ });
139
+
140
+ if (allMatch) {
141
+ return;
142
+ }
143
+
144
+ context.report({
145
+ loc: {
146
+ start: group[0].loc.start,
147
+ end: group[group.length - 1].loc.end,
148
+ },
149
+ messageId: "format",
150
+
151
+ fix(fixer) {
152
+ return fixer.replaceTextRange(
153
+ [group[0].range[0], group[group.length - 1].range[1]],
154
+ canonical
155
+ );
156
+ },
157
+ });
158
+ }
159
+
160
+ return {
161
+ Program() {
162
+ const comments = sourceCode.getAllComments();
163
+
164
+ let group = [];
165
+ let skipDirectiveContinuation = false;
166
+ let skipColumn = 0;
167
+ let skipLine = 0;
168
+
169
+ const flush = () => {
170
+ checkGroup(group);
171
+
172
+ group = [];
173
+ };
174
+
175
+ for (const comment of comments) {
176
+ if (
177
+ skipDirectiveContinuation &&
178
+ comment.type === "Line" &&
179
+ isStandalone(comment) &&
180
+ comment.loc.start.line === skipLine + 1 &&
181
+ comment.loc.start.column === skipColumn
182
+ ) {
183
+ skipLine = comment.loc.start.line;
184
+
185
+ if (!endsWithContinuation(comment)) {
186
+ skipDirectiveContinuation = false;
187
+ }
188
+
189
+ continue;
190
+ }
191
+
192
+ skipDirectiveContinuation = false;
193
+
194
+ const core = getCore(comment);
195
+ const isGroupable =
196
+ comment.type === "Line" &&
197
+ isStandalone(comment) &&
198
+ !isDirective(core) &&
199
+ (core.length > 0 || isBlankSeparatorLine(comment));
200
+
201
+ if (!isGroupable) {
202
+ flush();
203
+
204
+ if (
205
+ comment.type === "Line" &&
206
+ isStandalone(comment) &&
207
+ isDirective(core) &&
208
+ endsWithContinuation(comment)
209
+ ) {
210
+ skipDirectiveContinuation = true;
211
+ skipColumn = comment.loc.start.column;
212
+ skipLine = comment.loc.start.line;
213
+ }
214
+
215
+ continue;
216
+ }
217
+
218
+ if (group.length > 0) {
219
+ const previous = group[group.length - 1];
220
+ const isConsecutive =
221
+ comment.loc.start.line === previous.loc.start.line + 1 &&
222
+ comment.loc.start.column === previous.loc.start.column;
223
+
224
+ // A completed block (no trailing '\') must not absorb the next line
225
+ if (!endsWithContinuation(previous)) {
226
+ flush();
227
+ }
228
+
229
+ if (group.length > 0) {
230
+ const last = group[group.length - 1];
231
+ const stillConsecutive =
232
+ comment.loc.start.line === last.loc.start.line + 1 &&
233
+ comment.loc.start.column === last.loc.start.column;
234
+
235
+ // A boundary marker (e.g. 'Notice:') always starts a fresh group,
236
+ // so the previous comment is left untouched
237
+ if (!stillConsecutive || isBoundaryComment(comment)) {
238
+ flush();
239
+ }
240
+ }
241
+ }
242
+
243
+ group.push(comment);
244
+ }
245
+
246
+ flush();
247
+ },
248
+ };
249
+ },
250
+ };
@@ -0,0 +1,63 @@
1
+ const FORBIDDEN_TAG_REGEX = /^\s*\*\s*@(param|arg|argument|returns?|yields?)\b/;
2
+
3
+ export default {
4
+ meta: {
5
+ type: "suggestion",
6
+ docs: {
7
+ description:
8
+ "Forbid @param and @return tags (and their descriptions) in JSDoc, " +
9
+ "rely on the TypeScript signature instead.",
10
+ },
11
+ fixable: "code",
12
+ schema: [],
13
+ messages: {
14
+ noParamReturn:
15
+ "@param and @return tags are not permitted in JSDoc, rely on the " +
16
+ "TypeScript signature instead.",
17
+ },
18
+ },
19
+
20
+ create(context) {
21
+ const sourceCode = context.sourceCode || context.getSourceCode();
22
+
23
+ function checkComment(comment) {
24
+ if (comment.type !== "Block" || !comment.value.startsWith("*")) {
25
+ return;
26
+ }
27
+
28
+ const lines = comment.value.split("\n");
29
+
30
+ // Detect whether at least one forbidden tag line is present
31
+ const hasForbidden = lines.some((line) => {
32
+ return FORBIDDEN_TAG_REGEX.test(line);
33
+ });
34
+
35
+ if (!hasForbidden) {
36
+ return;
37
+ }
38
+
39
+ context.report({
40
+ node: comment,
41
+ messageId: "noParamReturn",
42
+
43
+ fix(fixer) {
44
+ const kept = lines.filter((line) => {
45
+ return !FORBIDDEN_TAG_REGEX.test(line);
46
+ });
47
+
48
+ return fixer.replaceText(comment, `/*${kept.join("\n")}*/`);
49
+ },
50
+ });
51
+ }
52
+
53
+ return {
54
+ "Program:exit"() {
55
+ const comments = sourceCode.getAllComments();
56
+
57
+ for (const comment of comments) {
58
+ checkComment(comment);
59
+ }
60
+ },
61
+ };
62
+ },
63
+ };