eslint-plugin-crisp 1.4.18 → 1.4.20

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
@@ -120,6 +120,7 @@ Each item has emojis denoting:
120
120
  | [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 | 🟠 |
121
121
  | [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 | 🟠 |
122
122
  | [crisp/align-requires](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/align-requires.js) | Enforces alignment of require statements | 🟠 |
123
+ | [crisp/arrow-block-newline](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/arrow-block-newline.js) | Requires a newline after `{` in arrow function blocks | 🟠 | 🟢 | 🟣 |
123
124
  | [crisp/const](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/const.js) | Enforces that `const` variables start with `__` or are all uppercase | 🟠 |
124
125
  | [crisp/constructor-variables](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/constructor-variables.js) | Ensures all class properties in the constructor start with `_` | 🟠 |
125
126
  | [crisp/enforce-optional](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/enforce-optional.js) | Enforces use of optional chaining | 🟠 | 🟢 | 🟣 |
@@ -220,10 +221,12 @@ Each item has emojis denoting:
220
221
  | [crisp/vue-attribute-comma](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-attribute-comma.js) | Disallows trailing comma after attribute | | 🟢 |
221
222
  | [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 | | 🟢 |
222
223
  | [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 | | 🟢 |
224
+ | [crisp/vue-define-component](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-define-component.js) | Enforces mixins and components to use the `defineComponent` wrapper | | 🟢 |
223
225
  | [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 | | 🟢 |
224
226
  | [crisp/vue-header-check](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-header-check.js) | Ensures `script`, `template` and `style` tags start with corresponding comment block | | 🟢 |
225
227
  | [crisp/vue-html-indent](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-html-indent.js) | Enforces consistent indentation in `template` (supports for Pug) | | 🟢 |
226
228
  | [crisp/vue-html-quotes](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-html-quotes.js) | Enforces HTML attributes to be enclosed with double quotes | | 🟢 |
229
+ | [crisp/vue-no-name-option](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-no-name-option.js) | Disallows explicit component names in Vue single-file components | | 🟢 |
227
230
  | [crisp/vue-no-regex-data](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-no-regex-data.js) | Disallows regular expressions to be declared in Vue data object | | 🟢 |
228
231
  | [crisp/vue-props-declaration-line-break](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-props-declaration-line-break.js) | Enforces line break between type and default function in prop definition | | 🟢 |
229
232
  | [crisp/vue-props-declaration-multiline](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-props-declaration-multiline.js) | Enforces props declarations to be multiline | | 🟢 |
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 ruleArrowBlockNewline from "./rules/arrow-block-newline.js";
9
10
  import ruleConsecutiveLineComments from "./rules/consecutive-line-comments.js";
10
11
  import ruleConst from "./rules/const.js";
11
12
  import ruleConstructorVariables from "./rules/constructor-variables.js";
@@ -44,12 +45,14 @@ import ruleVueAttributeComma from "./rules/vue-attribute-comma.js";
44
45
  import ruleVueAttributeLinebreak from "./rules/vue-attribute-linebreak.js";
45
46
  import ruleVueComputedOrder from "./rules/vue-computed-order.js";
46
47
  import ruleVueDataComment from "./rules/vue-data-comment.js";
48
+ import ruleVueDefineComponent from "./rules/vue-define-component.js";
47
49
  import ruleVueEmitsOrder from "./rules/vue-emits-order.js";
48
50
  import ruleVueHeaderCheck from "./rules/vue-header-check.js";
49
51
  import ruleVueHtmlIndent from "./rules/vue-html-indent.js";
50
52
  import ruleVueHtmlQuotes from "./rules/vue-html-quotes.js";
51
53
  import ruleVueMethodsSeparator from "./rules/vue-methods-separator.js";
52
54
  import ruleVueNameProp from "./rules/vue-name-prop.js";
55
+ import ruleVueNoNameOption from "./rules/vue-no-name-option.js";
53
56
  import ruleVueNoRegexData from "./rules/vue-no-regex-data.js";
54
57
  import ruleVuePropsDeclarationLineBreak from "./rules/vue-props-declaration-line-break.js";
55
58
  import ruleVuePropsDeclarationMultiline from "./rules/vue-props-declaration-multiline.js";
@@ -74,6 +77,7 @@ const plugin = {
74
77
  "align-consecutive-class-assignements": ruleAlignConsecutiveClassAssignements,
75
78
  "align-one-var": ruleAlignOneVar,
76
79
  "align-requires": ruleAlignRequires,
80
+ "arrow-block-newline": ruleArrowBlockNewline,
77
81
  "consecutive-line-comments": ruleConsecutiveLineComments,
78
82
  "const": ruleConst,
79
83
  "constructor-variables": ruleConstructorVariables,
@@ -112,12 +116,14 @@ const plugin = {
112
116
  "vue-attribute-linebreak": ruleVueAttributeLinebreak,
113
117
  "vue-computed-order": ruleVueComputedOrder,
114
118
  "vue-data-comment": ruleVueDataComment,
119
+ "vue-define-component": ruleVueDefineComponent,
115
120
  "vue-emits-order": ruleVueEmitsOrder,
116
121
  "vue-header-check": ruleVueHeaderCheck,
117
122
  "vue-html-indent": ruleVueHtmlIndent,
118
123
  "vue-html-quotes": ruleVueHtmlQuotes,
119
124
  "vue-methods-separator": ruleVueMethodsSeparator,
120
125
  "vue-name-prop": ruleVueNameProp,
126
+ "vue-no-name-option": ruleVueNoNameOption,
121
127
  "vue-no-regex-data": ruleVueNoRegexData,
122
128
  "vue-props-declaration-line-break": ruleVuePropsDeclarationLineBreak,
123
129
  "vue-props-declaration-multiline": ruleVuePropsDeclarationMultiline,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-crisp",
3
- "version": "1.4.18",
3
+ "version": "1.4.20",
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/arrow-block-newline": "error",
154
155
  "crisp/consecutive-line-comments": "error",
155
156
  "crisp/enforce-optional": "error",
156
157
  "crisp/header-check": "error",
@@ -167,6 +167,7 @@ export default function configRecommendedVue(pluginCrisp) {
167
167
 
168
168
  // Crisp JS rules
169
169
  "crisp/align-comments": "error",
170
+ "crisp/arrow-block-newline": "error",
170
171
  "crisp/consecutive-line-comments": "error",
171
172
  "crisp/const": "error",
172
173
  "crisp/enforce-optional": "error",
@@ -383,12 +384,14 @@ export default function configRecommendedVue(pluginCrisp) {
383
384
  "crisp/vue-attribute-linebreak": "error",
384
385
  "crisp/vue-computed-order": "error",
385
386
  "crisp/vue-data-comment": "error",
387
+ "crisp/vue-define-component": "error",
386
388
  "crisp/vue-emits-order": "error",
387
389
  "crisp/vue-header-check": "error",
388
390
  "crisp/vue-html-indent": "error",
389
391
  "crisp/vue-html-quotes": "error",
390
392
  "crisp/vue-methods-separator": "error",
391
393
  "crisp/vue-name-prop": "error",
394
+ "crisp/vue-no-name-option": "error",
392
395
  "crisp/vue-no-regex-data": "error",
393
396
  "crisp/vue-props-declaration-line-break": "error",
394
397
  "crisp/vue-props-declaration-multiline": "error",
package/recommended.js CHANGED
@@ -160,6 +160,7 @@ export default function configRecommended(pluginCrisp) {
160
160
  "crisp/align-consecutive-class-assignements": "error",
161
161
  "crisp/align-one-var": "error",
162
162
  "crisp/align-requires": "error",
163
+ "crisp/arrow-block-newline": "error",
163
164
  "crisp/const": "error",
164
165
  "crisp/constructor-variables": "error",
165
166
  "crisp/enforce-optional": "error",
@@ -0,0 +1,47 @@
1
+ export default {
2
+ meta: {
3
+ type: "layout",
4
+ docs: {
5
+ description:
6
+ "Require a newline after '{' in arrow function blocks.",
7
+ category: "Stylistic Issues",
8
+ recommended: false
9
+ },
10
+ schema: [],
11
+ messages: {
12
+ expectedNewline:
13
+ "Expected a newline after '{' in arrow function block."
14
+ }
15
+ },
16
+
17
+ create(context) {
18
+ const sourceCode = context.sourceCode || context.getSourceCode();
19
+
20
+ return {
21
+ ArrowFunctionExpression(node) {
22
+ if (node.body.type !== "BlockStatement") {
23
+ return;
24
+ }
25
+
26
+ const block = node.body;
27
+ const openBrace = sourceCode.getFirstToken(block);
28
+ const closeBrace = sourceCode.getLastToken(block);
29
+ const firstToken = sourceCode.getTokenAfter(openBrace);
30
+
31
+ // Empty blocks such as `() => {}` are allowed
32
+ if (!firstToken || firstToken === closeBrace) {
33
+ return;
34
+ }
35
+
36
+ if (firstToken.loc.start.line !== openBrace.loc.start.line) {
37
+ return;
38
+ }
39
+
40
+ context.report({
41
+ node: openBrace,
42
+ messageId: "expectedNewline"
43
+ });
44
+ }
45
+ };
46
+ }
47
+ };
@@ -0,0 +1,130 @@
1
+ export default {
2
+ meta: {
3
+ type: "suggestion",
4
+ docs: {
5
+ description: "enforce defineComponent wrapper for Vue mixins and components",
6
+ category: "Best Practices",
7
+ recommended: false
8
+ },
9
+ fixable: "code",
10
+ schema: []
11
+ },
12
+
13
+ create(context) {
14
+ const fileName = (context.filename || context.getFilename()).replace(/\\/g, "/");
15
+
16
+ if (fileName.endsWith("/index.js") || !isVueComponentOrMixin(fileName)) {
17
+ return {};
18
+ }
19
+
20
+ return {
21
+ ExportDefaultDeclaration(node) {
22
+ if (isDefineComponentCall(node.declaration)) {
23
+ return;
24
+ }
25
+
26
+ context.report({
27
+ node: node.declaration,
28
+ message: "Mixins and components must use the `defineComponent` wrapper.",
29
+
30
+ fix(fixer) {
31
+ return buildFixes(fixer, context, node);
32
+ }
33
+ });
34
+ }
35
+ };
36
+ }
37
+ };
38
+
39
+ // Returns whether the file is a Vue component or mixin
40
+ function isVueComponentOrMixin(fileName) {
41
+ return (
42
+ fileName.endsWith(".vue") ||
43
+ /Mixin\.js$/i.test(fileName) ||
44
+ fileName.includes("/mixins/")
45
+ );
46
+ }
47
+
48
+ // Returns whether the node is a defineComponent() call
49
+ function isDefineComponentCall(node) {
50
+ if (!node || node.type !== "CallExpression") {
51
+ return false;
52
+ }
53
+
54
+ if (node.callee.type === "Identifier") {
55
+ return node.callee.name === "defineComponent";
56
+ }
57
+
58
+ return (
59
+ node.callee.type === "MemberExpression" &&
60
+ node.callee.property.type === "Identifier" &&
61
+ node.callee.property.name === "defineComponent"
62
+ );
63
+ }
64
+
65
+ // Builds wrap + import fixes for a plain object default export
66
+ function buildFixes(fixer, context, exportNode) {
67
+ if (exportNode.declaration.type !== "ObjectExpression") {
68
+ return null;
69
+ }
70
+
71
+ const sourceCode = context.sourceCode || context.getSourceCode();
72
+ const importFix = buildDefineComponentImportFix(fixer, sourceCode);
73
+
74
+ if (importFix === false) {
75
+ return null;
76
+ }
77
+
78
+ const declarationText = sourceCode.getText(exportNode.declaration);
79
+ const fixes = [
80
+ fixer.replaceText(
81
+ exportNode.declaration,
82
+ `defineComponent(${declarationText})`
83
+ )
84
+ ];
85
+
86
+ if (importFix) {
87
+ fixes.unshift(importFix);
88
+ }
89
+
90
+ return fixes;
91
+ }
92
+
93
+ // Extends an existing named import from "vue" when needed
94
+ function buildDefineComponentImportFix(fixer, sourceCode) {
95
+ const body = sourceCode.ast.body;
96
+ const vueImport = body.find((node) => {
97
+ return node.type === "ImportDeclaration" && node.source.value === "vue";
98
+ });
99
+
100
+ if (!vueImport) {
101
+ return false;
102
+ }
103
+
104
+ const alreadyImported = vueImport.specifiers.some((specifier) => {
105
+ if (specifier.type !== "ImportSpecifier") {
106
+ return false;
107
+ }
108
+
109
+ const importedName = specifier.imported.name || specifier.imported.value;
110
+
111
+ return importedName === "defineComponent";
112
+ });
113
+
114
+ if (alreadyImported) {
115
+ return null;
116
+ }
117
+
118
+ const namedSpecifiers = vueImport.specifiers.filter((specifier) => {
119
+ return specifier.type === "ImportSpecifier";
120
+ });
121
+
122
+ if (namedSpecifiers.length === 0) {
123
+ return false;
124
+ }
125
+
126
+ return fixer.insertTextAfter(
127
+ namedSpecifiers[namedSpecifiers.length - 1],
128
+ ", defineComponent"
129
+ );
130
+ }
@@ -0,0 +1,108 @@
1
+ export default {
2
+ meta: {
3
+ type: "suggestion",
4
+ docs: {
5
+ description: "disallow explicit names in Vue single-file components",
6
+ category: "Best Practices",
7
+ recommended: false
8
+ },
9
+ fixable: "code",
10
+ schema: []
11
+ },
12
+
13
+ create(context) {
14
+ const fileName = (context.filename || context.getFilename()).replace(/\\/g, "/");
15
+
16
+ if (!fileName.endsWith(".vue")) {
17
+ return {};
18
+ }
19
+
20
+ return {
21
+ ExportDefaultDeclaration(node) {
22
+ const componentOptions = getComponentOptions(node.declaration);
23
+
24
+ if (!componentOptions) {
25
+ return;
26
+ }
27
+
28
+ componentOptions.properties.forEach((property) => {
29
+ if (!isNameProperty(property)) {
30
+ return;
31
+ }
32
+
33
+ context.report({
34
+ node: property,
35
+ message: "Vue single-file components must rely on their inferred filename.",
36
+
37
+ fix(fixer) {
38
+ return removeProperty(fixer, context, componentOptions, property);
39
+ }
40
+ });
41
+ });
42
+ }
43
+ };
44
+ }
45
+ };
46
+
47
+ // Returns the component options passed to defineComponent()
48
+ function getComponentOptions(node) {
49
+ if (!isDefineComponentCall(node)) {
50
+ return null;
51
+ }
52
+
53
+ const options = node.arguments[0];
54
+
55
+ return options?.type === "ObjectExpression" ? options : null;
56
+ }
57
+
58
+ // Returns whether the node is a defineComponent() call
59
+ function isDefineComponentCall(node) {
60
+ if (!node || node.type !== "CallExpression") {
61
+ return false;
62
+ }
63
+
64
+ if (node.callee.type === "Identifier") {
65
+ return node.callee.name === "defineComponent";
66
+ }
67
+
68
+ return (
69
+ node.callee.type === "MemberExpression" &&
70
+ node.callee.property.type === "Identifier" &&
71
+ node.callee.property.name === "defineComponent"
72
+ );
73
+ }
74
+
75
+ // Returns whether the property is the component name option
76
+ function isNameProperty(property) {
77
+ if (property.type !== "Property") {
78
+ return false;
79
+ }
80
+
81
+ if (property.key.type === "Identifier") {
82
+ return property.key.name === "name";
83
+ }
84
+
85
+ return property.key.type === "Literal" && property.key.value === "name";
86
+ }
87
+
88
+ // Removes a property and its adjacent comma
89
+ function removeProperty(fixer, context, options, property) {
90
+ const sourceCode = context.sourceCode || context.getSourceCode();
91
+ const nextToken = sourceCode.getTokenAfter(property);
92
+
93
+ if (nextToken?.value === ",") {
94
+ return fixer.removeRange([property.range[0], nextToken.range[1]]);
95
+ }
96
+
97
+ const propertyIndex = options.properties.indexOf(property);
98
+
99
+ if (propertyIndex > 0) {
100
+ const previousToken = sourceCode.getTokenBefore(property);
101
+
102
+ if (previousToken?.value === ",") {
103
+ return fixer.removeRange([previousToken.range[0], property.range[1]]);
104
+ }
105
+ }
106
+
107
+ return fixer.remove(property);
108
+ }