eslint-plugin-crisp 1.0.90 → 1.0.92

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
@@ -151,6 +151,7 @@ Each item has emojis denoting:
151
151
  | [crisp/jsdoc-check-optional-params](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/jsdoc-check-optional-params.js) | Requires optional parameters to be surrounded by brackets | | 🟢 |
152
152
  | [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 | | 🟢 |
153
153
  | [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` | 🟠 | 🟢 |
154
+ | [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 | 🟠 | 🟢 |
154
155
 
155
156
  #### General Vue rules
156
157
 
@@ -191,6 +192,7 @@ Each item has emojis denoting:
191
192
 
192
193
  | Name | Description | 🟠 | 🟢 |
193
194
  | :- | :- | :- | :- |
195
+ | [crisp/vue-attribute-comma](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-attribute-comma.js) | Disallows trailing comma after attribute | | 🟢 |
194
196
  | [crisp/vue-attribute-linebreak](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-attribute-linebreak.js) | Enforces linebreak before first attribute and after last attribute | | 🟢 |
195
197
  | [crisp/vue-computed-order](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-computed-order.js) | Ensures computed properties are alphabetically ordered | | 🟢 |
196
198
  | [crisp/vue-emits-order](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-emits-order.js) | Ensures emits properties are alphabetically ordered | | 🟢 |
package/index.js CHANGED
@@ -20,6 +20,7 @@ module.exports = {
20
20
  "jsdoc-check-optional-params": require("./rules/jsdoc-check-optional-params"),
21
21
  "jsdoc-enforce-access": require("./rules/jsdoc-enforce-access"),
22
22
  "jsdoc-enforce-classdesc": require("./rules/jsdoc-enforce-classdesc"),
23
+ "jsdoc-require-description-uppercase": require("./rules/jsdoc-require-description-uppercase"),
23
24
  "methods-naming": require("./rules/methods-naming"),
24
25
  "methods-ordering": require("./rules/methods-ordering"),
25
26
  "multiline-comment-end-backslash": require("./rules/multiline-comment-end-backslash"),
@@ -34,6 +35,7 @@ module.exports = {
34
35
  "ternary-parenthesis": require("./rules/ternary-parenthesis"),
35
36
  "two-lines-between-class-members": require("./rules/two-lines-between-class-members"),
36
37
  "variable-names": require("./rules/variable-names"),
38
+ "vue-attribute-comma": require("./rules/vue-attribute-comma"),
37
39
  "vue-attribute-linebreak": require("./rules/vue-attribute-linebreak"),
38
40
  "vue-computed-order": require("./rules/vue-computed-order"),
39
41
  "vue-emits-order": require("./rules/vue-emits-order"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-crisp",
3
- "version": "1.0.90",
3
+ "version": "1.0.92",
4
4
  "description": "Custom ESLint Rules for Crisp",
5
5
  "author": "Crisp IM SAS",
6
6
  "main": "index.js",
@@ -222,6 +222,7 @@ module.exports = {
222
222
  "crisp/jsdoc-check-optional-params": "error",
223
223
  "crisp/jsdoc-enforce-access": "error",
224
224
  "crisp/jsdoc-enforce-classdesc": "error",
225
+ "crisp/jsdoc-require-description-uppercase": "error",
225
226
 
226
227
  // General Vue rules
227
228
  "vue/attributes-order": [
@@ -334,6 +335,7 @@ module.exports = {
334
335
  ],
335
336
 
336
337
  // Crisp Vue rules
338
+ "crisp/vue-attribute-comma": "error",
337
339
  "crisp/vue-attribute-linebreak": "error",
338
340
  "crisp/vue-computed-order": "error",
339
341
  "crisp/vue-emits-order": "error",
package/recommended.js CHANGED
@@ -209,6 +209,7 @@ module.exports = {
209
209
  // Crisp JSDoc rules
210
210
  "crisp/jsdoc-align-params": "error",
211
211
  "crisp/jsdoc-check-indentation": "error",
212
- "crisp/jsdoc-enforce-classdesc": "error"
212
+ "crisp/jsdoc-enforce-classdesc": "error",
213
+ "crisp/jsdoc-require-description-uppercase": "error"
213
214
  }
214
215
  }
@@ -0,0 +1,224 @@
1
+ // Based on https://github.com/gajus/eslint-plugin-jsdoc/blob/main/src/rules/requireDescriptionCompleteSentence.js
2
+ // Removed handling of sentences, punctuation, abbreviations. We just need to check that the description starts with an uppercase character.
3
+
4
+ const { default: iterateJsdoc } = require("eslint-plugin-jsdoc/dist/iterateJsdoc");
5
+ const escapeStringRegexp = require("escape-string-regexp");
6
+
7
+ const otherDescriptiveTags = new Set([
8
+ // 'copyright' and 'see' might be good addition, but as the former may be
9
+ // sensitive text, and the latter may have just a link, they are not
10
+ // included by default
11
+ "summary", "file", "fileoverview", "overview", "classdesc", "todo",
12
+ "deprecated", "throws", "exception", "yields", "yield",
13
+ ]);
14
+
15
+ /**
16
+ * @param {string} text
17
+ * @returns {string[]}
18
+ */
19
+ const extractParagraphs = (text) => {
20
+ return text.split(/(?<![;:])\n\n+/u);
21
+ };
22
+
23
+ /**
24
+ * @param {string} str
25
+ * @returns {boolean}
26
+ */
27
+ const isCapitalized = (str) => {
28
+ return str[0] === str[0].toUpperCase();
29
+ };
30
+
31
+ /**
32
+ * @param {string} str
33
+ * @returns {boolean}
34
+ */
35
+ const isTable = (str) => {
36
+ return str.charAt(0) === "|";
37
+ };
38
+
39
+ /**
40
+ * @param {string} str
41
+ * @returns {string}
42
+ */
43
+ const capitalize = (str) => {
44
+ return str.charAt(0).toUpperCase() + str.slice(1);
45
+ };
46
+
47
+ /**
48
+ * @param {string} description
49
+ * @param {import('../iterateJsdoc.js').Report} reportOrig
50
+ * @param {import('eslint').Rule.Node} jsdocNode
51
+ * @param {import('eslint').SourceCode} sourceCode
52
+ * @param {import('comment-parser').Spec|{
53
+ * line: import('../iterateJsdoc.js').Integer
54
+ * }} tag
55
+ * @returns {boolean}
56
+ */
57
+ const validateDescription = (
58
+ description, reportOrig, jsdocNode, sourceCode, tag,
59
+ ) => {
60
+ if (!description || (/^\n+$/u).test(description)) {
61
+ return false;
62
+ }
63
+
64
+ const descriptionNoHeadings = description.replaceAll(/^\s*#[^\n]*(\n|$)/gm, "");
65
+
66
+ const paragraphs = extractParagraphs(descriptionNoHeadings).filter(Boolean);
67
+
68
+ return paragraphs.some((paragraph, parIdx) => {
69
+ const sentences = [paragraph];
70
+
71
+ const fix = /** @type {import('eslint').Rule.ReportFixer} */ (fixer) => {
72
+ let text = sourceCode.getText(jsdocNode);
73
+
74
+ for (const sentence of sentences.filter((sentence_) => {
75
+ return !(/^\s*$/u).test(sentence_) && !isCapitalized(sentence_) &&
76
+ !isTable(sentence_);
77
+ })) {
78
+ const beginning = sentence.split("\n")[0];
79
+
80
+ if ("tag" in tag && tag.tag) {
81
+ const reg = new RegExp(`(@${escapeStringRegexp(tag.tag)}.*)${escapeStringRegexp(beginning)}`, "u");
82
+
83
+ text = text.replace(reg, (_$0, $1) => {
84
+ return $1 + capitalize(beginning);
85
+ });
86
+ } else {
87
+ text = text.replace(new RegExp("((?:[.?!]|\\*|\\})\\s*)" + escapeStringRegexp(beginning), "u"), "$1" + capitalize(beginning));
88
+ }
89
+ }
90
+
91
+ return fixer.replaceText(jsdocNode, text);
92
+ };
93
+
94
+ /**
95
+ * @param {string} msg
96
+ * @param {import('eslint').Rule.ReportFixer | null | undefined} fixer
97
+ * @param {{
98
+ * line?: number | undefined;
99
+ * column?: number | undefined;
100
+ * } | (import('comment-parser').Spec & {
101
+ * line?: number | undefined;
102
+ * column?: number | undefined;
103
+ * })} tagObj
104
+ * @returns {void}
105
+ */
106
+ const report = (msg, fixer, tagObj) => {
107
+ if ("line" in tagObj) {
108
+ /**
109
+ * @type {{
110
+ * line: number;
111
+ * }}
112
+ */ (tagObj).line += parIdx * 2;
113
+ } else {
114
+ /** @type {import('comment-parser').Spec} */ (
115
+ tagObj
116
+ ).source[0].number += parIdx * 2;
117
+ }
118
+
119
+ // Avoid errors if old column doesn't exist here
120
+ tagObj.column = 0;
121
+ reportOrig(msg, fixer, tagObj);
122
+ };
123
+
124
+ if (sentences.some((sentence) => {
125
+ return !(/^\s*$/u).test(sentence) && !isCapitalized(sentence) && !isTable(sentence);
126
+ })) {
127
+ report("Sentences should start with an uppercase character.", fix, tag);
128
+ }
129
+
130
+ return false;
131
+ });
132
+ };
133
+
134
+ module.exports = iterateJsdoc(({
135
+ sourceCode,
136
+ context,
137
+ jsdoc,
138
+ report,
139
+ jsdocNode,
140
+ utils,
141
+ }) => {
142
+ let {
143
+ description,
144
+ } = utils.getDescription();
145
+
146
+ const indices = [
147
+ ...description.matchAll(/```[\s\S]*```/gu),
148
+ ].map((match) => {
149
+ const {
150
+ index,
151
+ } = match;
152
+ const [
153
+ {
154
+ length,
155
+ },
156
+ ] = match;
157
+ return {
158
+ index,
159
+ length,
160
+ };
161
+ }).reverse();
162
+
163
+ for (const {
164
+ index,
165
+ length,
166
+ } of indices) {
167
+ description = description.slice(0, index) +
168
+ description.slice(/** @type {import('../iterateJsdoc.js').Integer} */ (
169
+ index
170
+ ) + length);
171
+ }
172
+
173
+ if (validateDescription(description, report, jsdocNode, sourceCode, {
174
+ line: jsdoc.source[0].number + 1,
175
+ })) {
176
+ return;
177
+ }
178
+
179
+ utils.forEachPreferredTag("description", (matchingJsdocTag) => {
180
+ const desc = `${matchingJsdocTag.name} ${utils.getTagDescription(matchingJsdocTag)}`.trim();
181
+ validateDescription(desc, report, jsdocNode, sourceCode, matchingJsdocTag);
182
+ }, true);
183
+
184
+ const {
185
+ tagsWithNames,
186
+ } = utils.getTagsByType(jsdoc.tags);
187
+ const tagsWithoutNames = utils.filterTags(({
188
+ tag: tagName,
189
+ }) => {
190
+ return otherDescriptiveTags.has(tagName) ||
191
+ utils.hasOptionTag(tagName) && !tagsWithNames.some(({
192
+ tag,
193
+ }) => {
194
+ // If user accidentally adds tags with names (or like `returns`
195
+ // get parsed as having names), do not add to this list
196
+ return tag === tagName;
197
+ });
198
+ });
199
+
200
+ tagsWithNames.some((tag) => {
201
+ const desc = /** @type {string} */ (
202
+ utils.getTagDescription(tag)
203
+ ).replace(/^- /u, "").trimEnd();
204
+
205
+ return validateDescription(desc, report, jsdocNode, sourceCode, tag);
206
+ });
207
+
208
+ tagsWithoutNames.some((tag) => {
209
+ const desc = `${tag.name} ${utils.getTagDescription(tag)}`.trim();
210
+
211
+ return validateDescription(desc, report, jsdocNode, sourceCode, tag);
212
+ });
213
+ }, {
214
+ iterateAllJsdocs: true,
215
+ meta: {
216
+ docs: {
217
+ description: "Requires that block description, explicit `@description`, and `@param`/`@returns` tag descriptions are written in complete sentences.",
218
+ url: "https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-description-complete-sentence.md#repos-sticky-header",
219
+ },
220
+ fixable: "code",
221
+ schema: [],
222
+ type: "suggestion",
223
+ },
224
+ });
@@ -0,0 +1,33 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: "problem",
4
+ docs: {
5
+ description: "disallow commas after attribute values in Vue templates",
6
+ category: "Possible Errors",
7
+ recommended: false,
8
+ },
9
+ fixable: "code",
10
+ schema: [], // no options
11
+ },
12
+
13
+ create(context) {
14
+ return context.parserServices.defineTemplateBodyVisitor({
15
+ 'VAttribute'(node) {
16
+ const sourceCode = context.getSourceCode();
17
+ const attributeText = sourceCode.getText(node);
18
+ const attributeEndIndex = node.range[1];
19
+ const nextChar = sourceCode.getText().slice(attributeEndIndex, attributeEndIndex + 1);
20
+
21
+ if (nextChar === ',') {
22
+ context.report({
23
+ node,
24
+ message: 'Comma after attribute value is not allowed.',
25
+ fix(fixer) {
26
+ return fixer.removeRange([attributeEndIndex, attributeEndIndex + 1]);
27
+ },
28
+ });
29
+ }
30
+ },
31
+ });
32
+ },
33
+ };