eslint-plugin-crisp 1.0.20 → 1.0.22

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
@@ -10,9 +10,11 @@ module.exports = {
10
10
  "constructor-variables": require("./rules/constructor-variables"),
11
11
  "header-check": require("./rules/header-check"),
12
12
  "header-comments-check": require("./rules/header-comments-check"),
13
+ "jsdoc-align-params": require("./rules/jsdoc-align-params"),
14
+ "jsdoc-check-indentation": require("./rules/jsdoc-check-indentation"),
15
+ "jsdoc-check-optional-params": require("./rules/jsdoc-check-optional-params"),
13
16
  "jsdoc-enforce-access": require("./rules/jsdoc-enforce-access"),
14
17
  "jsdoc-enforce-classdesc": require("./rules/jsdoc-enforce-classdesc"),
15
- "jsdoc-check-optional-params": require("./rules/jsdoc-check-optional-params"),
16
18
  "methods-naming": require("./rules/methods-naming"),
17
19
  "multiline-comment-end-backslash": require("./rules/multiline-comment-end-backslash"),
18
20
  "no-async": require("./rules/no-async"),
@@ -24,10 +26,12 @@ module.exports = {
24
26
  "regex-in-constructor": require("./rules/regex-in-constructor"),
25
27
  "ternary-parenthesis": require("./rules/ternary-parenthesis"),
26
28
  "multiline-comment-end-backslash": require("./rules/multiline-comment-end-backslash"),
27
- "jsdocs-align-params": require("./rules/jsdocs-align-params"),
28
29
  "two-lines-between-class-members": require("./rules/two-lines-between-class-members"),
29
30
  "variable-names": require("./rules/variable-names"),
30
31
  "align-consecutive-class-assignements": require("./rules/align-consecutive-class-assignements"),
32
+ "vue-computed-order": require("./rules/vue-computed-order"),
33
+ "vue-emits-order": require("./rules/vue-emits-order"),
34
+ "vue-header-check": require("./rules/vue-header-check"),
31
35
  "vue-html-quotes": require("./rules/vue-html-quotes"),
32
36
  "vue-props-declaration-order": require("./rules/vue-props-declaration-order")
33
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-crisp",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "Custom EsLint Rules for Crisp",
5
5
  "main": "index.js",
6
6
  "author": "Crisp IM SAS",
@@ -34,6 +34,7 @@ module.exports = {
34
34
  "no-trailing-spaces": "error",
35
35
  "no-tabs": "error",
36
36
  "object-curly-spacing": ["error", "always"],
37
+ "prefer-arrow-callback": "error",
37
38
  "semi": ["error", "always"],
38
39
  "semi-style": ["error", "last"],
39
40
  "semi-spacing": ["error", { "before": false, "after": true }],
@@ -47,6 +48,8 @@ module.exports = {
47
48
  { blankLine: "always", prev: "const", next: "*" },
48
49
  { blankLine: "any", prev: "const", next: "const" },
49
50
  { blankLine: "always", prev: "block-like", next: "*" },
51
+ { blankLine: "always", prev: "*", next: "break" },
52
+ { blankLine: "any", prev: "empty", next: "break" },
50
53
  { blankLine: "always", prev: "*", next: "block-like" },
51
54
  { blankLine: "any", prev: "case", next: "case" },
52
55
  { blankLine: "always", prev: "continue", next: "*" },
@@ -59,10 +62,29 @@ module.exports = {
59
62
  { "avoidEscape": true, "allowTemplateLiterals": true }
60
63
  ],
61
64
 
65
+ // Crisp JS rules
66
+ "crisp/header-check": "error",
67
+ "crisp/header-comments-check": "error",
68
+ "crisp/methods-naming": "error",
69
+ "crisp/multiline-comment-end-backslash": "error",
70
+ "crisp/no-async": "error",
71
+ "crisp/no-var-in-blocks": "error",
72
+ "crisp/no-useless-template-literals": "error",
73
+ "crisp/one-space-after-operator": ["error", { "checkColon": false }],
74
+ "crisp/regex-in-constructor": "error",
75
+ "crisp/ternary-parenthesis": "error",
76
+ "crisp/variable-names": "error",
77
+ "crisp/no-short-parameters": [
78
+ "error",
79
+
80
+ {
81
+ exceptions: ["_", "$", "x", "y"]
82
+ }
83
+ ],
84
+
62
85
  // JSDoc rules
63
86
  "jsdoc/require-param-description": "off",
64
87
  "jsdoc/newline-after-description": "off",
65
- "jsdoc/check-indentation": "error",
66
88
  "jsdoc/require-jsdoc": [
67
89
  "error",
68
90
 
@@ -100,14 +122,17 @@ module.exports = {
100
122
  ],
101
123
 
102
124
  // Crisp JSDoc rules
125
+ "crisp/jsdoc-align-params": "error",
126
+ "crisp/jsdoc-check-indentation": "error",
103
127
  "crisp/jsdoc-check-optional-params": "error",
104
128
  "crisp/jsdoc-enforce-access": "error",
105
129
  "crisp/jsdoc-enforce-classdesc": "error",
106
- "crisp/jsdocs-align-params": "error",
107
130
 
108
131
  // General Vue rules
109
- "vue/no-v-html": "off",
110
132
  "vue/html-quotes": "off",
133
+ "vue/no-v-html": "off",
134
+ "vue/prefer-prop-type-boolean-first": "error",
135
+ "vue/prefer-true-attribute-shorthand": "error",
111
136
  "vue/attributes-order": [
112
137
  "error",
113
138
 
@@ -151,26 +176,10 @@ module.exports = {
151
176
  }
152
177
  ],
153
178
 
154
- // Crisp JS rules
155
- "crisp/header-check": "error",
156
- "crisp/header-comments-check": "error",
157
- "crisp/methods-naming": "error",
158
- "crisp/multiline-comment-end-backslash": "error",
159
- "crisp/no-async": "error",
160
- "crisp/no-var-in-blocks": "error",
161
- "crisp/no-useless-template-literals": "error",
162
- "crisp/one-space-after-operator": ["error", { "checkColon": false }],
163
- "crisp/regex-in-constructor": "error",
164
- "crisp/variable-names": "error",
165
- "crisp/no-short-parameters": [
166
- "error",
167
-
168
- {
169
- exceptions: ["_", "$", "x", "y"]
170
- }
171
- ],
172
-
173
179
  // Crisp Vue rules
180
+ "crisp/vue-computed-order": "error",
181
+ "crisp/vue-emits-order": "error",
182
+ "crisp/vue-header-check": "error",
174
183
  "crisp/vue-html-quotes": "error",
175
184
  "crisp/vue-props-declaration-order": "error"
176
185
  },
package/recommended.js CHANGED
@@ -46,6 +46,8 @@ module.exports = {
46
46
  { blankLine: "always", prev: "const", next: "*" },
47
47
  { blankLine: "any", prev: "const", next: "const" },
48
48
  { blankLine: "always", prev: "block-like", next: "*" },
49
+ { blankLine: "always", prev: "*", next: "break" },
50
+ { blankLine: "any", prev: "empty", next: "break" },
49
51
  { blankLine: "always", prev: "*", next: "block-like" },
50
52
  { blankLine: "any", prev: "case", next: "case" },
51
53
  { blankLine: "always", prev: "continue", next: "*" },
@@ -61,7 +63,6 @@ module.exports = {
61
63
  // JSDoc rules
62
64
  "jsdoc/require-param-description": "off",
63
65
  "jsdoc/newline-after-description": "off",
64
- "jsdoc/check-indentation": "error",
65
66
  "jsdoc/require-jsdoc": [
66
67
  "error",
67
68
 
@@ -99,8 +100,9 @@ module.exports = {
99
100
  ],
100
101
 
101
102
  // Crisp JSDoc rules
103
+ "crisp/jsdoc-align-params": "error",
104
+ "crisp/jsdoc-check-indentation": "error",
102
105
  "crisp/jsdoc-enforce-classdesc": "error",
103
- "crisp/jsdocs-align-params": "error",
104
106
 
105
107
  // Crisp JS rules
106
108
  "crisp/align-one-var": "error",
@@ -2,7 +2,7 @@ module.exports = {
2
2
  meta: {
3
3
  type: 'layout',
4
4
  docs: {
5
- description: 'enforce alignment for JSDocs',
5
+ description: 'enforce alignment for JSDoc',
6
6
  category: 'Stylistic Issues',
7
7
  recommended: false,
8
8
  },
@@ -0,0 +1,95 @@
1
+ // Based on https://github.com/gajus/eslint-plugin-jsdoc/blob/main/src/rules/checkIndentation.js
2
+
3
+ const { default: iterateJsdoc } = require("eslint-plugin-jsdoc/dist/iterateJsdoc");
4
+
5
+ /**
6
+ * @param {string} str
7
+ * @param {string[]} excludeTags
8
+ * @returns {string}
9
+ */
10
+ const maskExcludedContent = (str, excludeTags) => {
11
+ const regContent = new RegExp(`([ \\t]+\\*)[ \\t]@(?:${excludeTags.join('|')})(?=[ \\n])([\\w|\\W]*?\\n)(?=[ \\t]*\\*(?:[ \\t]*@\\w+\\s|\\/))`, 'gu');
12
+
13
+ return str.replace(regContent, (_match, margin, code) => {
14
+ return (margin + '\n').repeat(code.match(/\n/gu).length);
15
+ });
16
+ };
17
+
18
+ /**
19
+ * @param {string} str
20
+ * @returns {string}
21
+ */
22
+ const maskCodeBlocks = (str) => {
23
+ const regContent = /([ \t]+\*)[ \t]```[^\n]*?([\w|\W]*?\n)(?=[ \t]*\*(?:[ \t]*(?:```|@\w+\s)|\/))/gu;
24
+
25
+ return str.replace(regContent, (_match, margin, code) => {
26
+ return (margin + '\n').repeat(code.match(/\n/gu).length);
27
+ });
28
+ };
29
+
30
+ const maskLineContinuations = (str) => {
31
+ // Match a line ending with a backslash (first capture group) and the \
32
+ // line following it (second capture group)
33
+ const regContent = /(\*\s.*\\)\n(\s*\*\s+.+)/gu;
34
+
35
+ return str.replace(regContent, (_match, lineBeforeContinuation, lineAfterContinuation) => {
36
+ // Normalize the line after the continuation to remove any extra spaces \
37
+ // after the '*'. The goal is to treat the continuation line as a \
38
+ // direct extension of the previous line.
39
+ const normalizedLine = lineAfterContinuation.replace(/(\*\s)(.+)/, "$1$2");
40
+
41
+ // Return the combined line (previous line + continuation line)
42
+ return lineBeforeContinuation + normalizedLine;
43
+ });
44
+ }
45
+
46
+ module.exports = iterateJsdoc(({
47
+ sourceCode,
48
+ jsdocNode,
49
+ report,
50
+ context,
51
+ }) => {
52
+ const options = context.options[0] || {};
53
+ const /** @type {{excludeTags: string[]}} */ {
54
+ excludeTags = [
55
+ 'example',
56
+ ],
57
+ } = options;
58
+
59
+ const reg = /^(?:\/?\**|[ \t]*)\*[ \t]{2}/gmu;
60
+
61
+ const textWithoutCodeBlocks = maskCodeBlocks(sourceCode.getText(jsdocNode));
62
+ const textWithoutExcluded = excludeTags.length ? maskExcludedContent(textWithoutCodeBlocks, excludeTags) : textWithoutCodeBlocks;
63
+ const text = maskLineContinuations(textWithoutExcluded);
64
+
65
+ if (reg.test(text)) {
66
+ const lineBreaks = text.slice(0, reg.lastIndex).match(/\n/gu) || [];
67
+ report('There must be no indentation.', null, {
68
+ line: lineBreaks.length,
69
+ });
70
+ }
71
+ }, {
72
+ iterateAllJsdocs: true,
73
+ meta: {
74
+ docs: {
75
+ description: 'Reports invalid padding inside JSDoc blocks.',
76
+ url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-indentation.md#repos-sticky-header',
77
+ },
78
+ schema: [
79
+ {
80
+ additionalProperties: false,
81
+ properties: {
82
+ excludeTags: {
83
+ items: {
84
+ pattern: '^\\S+$',
85
+ type: 'string',
86
+ },
87
+ type: 'array',
88
+ },
89
+ },
90
+ type: 'object',
91
+ },
92
+ ],
93
+ type: 'layout',
94
+ },
95
+ });
@@ -7,6 +7,7 @@ module.exports = {
7
7
  recommended: false,
8
8
  },
9
9
  schema: [], // no options
10
+ fixable: "code",
10
11
  },
11
12
  create(context) {
12
13
  return {
@@ -20,6 +21,13 @@ module.exports = {
20
21
  context.report({
21
22
  node,
22
23
  message: "The condition in ternary expressions with an operator should be wrapped in parentheses",
24
+ fix(fixer) {
25
+ const conditionText = sourceCode.getText(node.test);
26
+ return [
27
+ fixer.insertTextBefore(node.test, "("),
28
+ fixer.insertTextAfter(node.test, ")"),
29
+ ];
30
+ },
23
31
  });
24
32
  }
25
33
  }
@@ -0,0 +1,40 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: "suggestion",
4
+ docs: {
5
+ description: "enforce computed properties alphabetical order",
6
+ category: "Stylistic Issues",
7
+ recommended: false
8
+ },
9
+ fixable: "code",
10
+ schema: [] // no options
11
+ },
12
+
13
+ create(context) {
14
+ return {
15
+ 'ExportDefaultDeclaration Property[key.name="computed"]'(node) {
16
+ const properties = node.value.properties;
17
+
18
+ properties.slice(0, -1).forEach((property, index) => {
19
+ const nextProperty = properties[index + 1];
20
+ if (property.key.name > nextProperty.key.name) {
21
+ context.report({
22
+ node: nextProperty,
23
+ message: "Computed properties should be in alphabetical order.",
24
+ fix(fixer) {
25
+ const sourceCode = context.getSourceCode();
26
+ const propertyText = sourceCode.getText(property);
27
+ const nextPropertyText = sourceCode.getText(nextProperty);
28
+
29
+ return [
30
+ fixer.replaceText(property, nextPropertyText),
31
+ fixer.replaceText(nextProperty, propertyText)
32
+ ];
33
+ }
34
+ });
35
+ }
36
+ });
37
+ }
38
+ };
39
+ }
40
+ };
@@ -0,0 +1,40 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: "suggestion",
4
+ docs: {
5
+ description: "enforce emits alphabetical order",
6
+ category: "Stylistic Issues",
7
+ recommended: false
8
+ },
9
+ fixable: "code",
10
+ schema: [] // no options
11
+ },
12
+
13
+ create(context) {
14
+ return {
15
+ 'ExportDefaultDeclaration Property[key.name="emits"]'(node) {
16
+ const elements = node.value.elements;
17
+
18
+ elements.slice(0, -1).forEach((element, index) => {
19
+ const nextElement = elements[index + 1];
20
+ if (element.value > nextElement.value) {
21
+ context.report({
22
+ node: nextElement,
23
+ message: "Emits should be in alphabetical order.",
24
+ fix(fixer) {
25
+ const sourceCode = context.getSourceCode();
26
+ const elementText = sourceCode.getText(element);
27
+ const nextElementText = sourceCode.getText(nextElement);
28
+
29
+ return [
30
+ fixer.replaceText(element, nextElementText),
31
+ fixer.replaceText(nextElement, elementText)
32
+ ];
33
+ }
34
+ });
35
+ }
36
+ });
37
+ }
38
+ };
39
+ }
40
+ };
@@ -0,0 +1,48 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ module.exports = {
5
+ meta: {
6
+ type: "suggestion",
7
+ docs: {
8
+ description: "Enforce Vue section headers",
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
+ if (fileExtension === ".vue") {
25
+ // Define the headers
26
+ const headers = ["TEMPLATE", "SCRIPT", "STYLE"];
27
+
28
+ // Define the Vue tags
29
+ const vueTags = ["template", "script", "style"];
30
+
31
+ // Check each tag individually
32
+ vueTags.forEach((tag, index) => {
33
+ const tagRegExp = new RegExp(`<${tag}`);
34
+ const headerRegExp = new RegExp(`<!--\\s*\\*{73}\\s*\\n\\s*${headers[index]}\\s*\\n\\s*\\*{73}\\s*-->\\s*\\n\\s*<${tag}`);
35
+
36
+ // If the tag exists without the corresponding header, report an error
37
+ if (tagRegExp.test(fileContent) && !headerRegExp.test(fileContent)) {
38
+ context.report({
39
+ node,
40
+ message: `The '<${tag}>' section must be preceded by the '${headers[index]}' header comment.`,
41
+ });
42
+ }
43
+ });
44
+ }
45
+ }
46
+ };
47
+ }
48
+ };