eslint-plugin-crisp 1.4.17 → 1.4.19
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 +2 -0
- package/index.js +4 -0
- package/package.json +1 -1
- package/recommended-ts.js +1 -0
- package/recommended-vue.js +3 -0
- package/recommended.js +1 -0
- package/rules/arrow-block-newline.js +47 -0
- package/rules/const.js +144 -12
- package/rules/variable-names.js +35 -100
- package/rules/vue-define-component.js +130 -0
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,6 +221,7 @@ 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) | | 🟢 |
|
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,6 +45,7 @@ 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";
|
|
@@ -74,6 +76,7 @@ const plugin = {
|
|
|
74
76
|
"align-consecutive-class-assignements": ruleAlignConsecutiveClassAssignements,
|
|
75
77
|
"align-one-var": ruleAlignOneVar,
|
|
76
78
|
"align-requires": ruleAlignRequires,
|
|
79
|
+
"arrow-block-newline": ruleArrowBlockNewline,
|
|
77
80
|
"consecutive-line-comments": ruleConsecutiveLineComments,
|
|
78
81
|
"const": ruleConst,
|
|
79
82
|
"constructor-variables": ruleConstructorVariables,
|
|
@@ -112,6 +115,7 @@ const plugin = {
|
|
|
112
115
|
"vue-attribute-linebreak": ruleVueAttributeLinebreak,
|
|
113
116
|
"vue-computed-order": ruleVueComputedOrder,
|
|
114
117
|
"vue-data-comment": ruleVueDataComment,
|
|
118
|
+
"vue-define-component": ruleVueDefineComponent,
|
|
115
119
|
"vue-emits-order": ruleVueEmitsOrder,
|
|
116
120
|
"vue-header-check": ruleVueHeaderCheck,
|
|
117
121
|
"vue-html-indent": ruleVueHtmlIndent,
|
package/package.json
CHANGED
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",
|
package/recommended-vue.js
CHANGED
|
@@ -167,7 +167,9 @@ 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",
|
|
172
|
+
"crisp/const": "error",
|
|
171
173
|
"crisp/enforce-optional": "error",
|
|
172
174
|
"crisp/header-check": "error",
|
|
173
175
|
"crisp/header-comments-check": "error",
|
|
@@ -382,6 +384,7 @@ export default function configRecommendedVue(pluginCrisp) {
|
|
|
382
384
|
"crisp/vue-attribute-linebreak": "error",
|
|
383
385
|
"crisp/vue-computed-order": "error",
|
|
384
386
|
"crisp/vue-data-comment": "error",
|
|
387
|
+
"crisp/vue-define-component": "error",
|
|
385
388
|
"crisp/vue-emits-order": "error",
|
|
386
389
|
"crisp/vue-header-check": "error",
|
|
387
390
|
"crisp/vue-html-indent": "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
|
+
};
|
package/rules/const.js
CHANGED
|
@@ -1,27 +1,159 @@
|
|
|
1
|
+
const isTopLevelDeclaration = (node) => {
|
|
2
|
+
let parent = node.parent;
|
|
3
|
+
|
|
4
|
+
if (parent?.type === "ExportNamedDeclaration") {
|
|
5
|
+
parent = parent.parent;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return parent?.type === "Program";
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const isExportedDeclaration = (node) => {
|
|
12
|
+
return node.parent?.type === "ExportNamedDeclaration";
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const isCallOrConstruction = (node) => {
|
|
16
|
+
return node?.type === "CallExpression" || node?.type === "NewExpression";
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const isFunctionExpression = (node) => {
|
|
20
|
+
return (
|
|
21
|
+
node?.type === "ArrowFunctionExpression" ||
|
|
22
|
+
node?.type === "FunctionExpression"
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
1
26
|
export default {
|
|
2
27
|
meta: {
|
|
3
28
|
type: "suggestion",
|
|
4
29
|
docs: {
|
|
5
|
-
description:
|
|
30
|
+
description:
|
|
31
|
+
"Enforce top-level uppercase or double-underscore constant names.",
|
|
6
32
|
category: "Stylistic Issues",
|
|
7
|
-
recommended: false
|
|
33
|
+
recommended: false
|
|
8
34
|
},
|
|
9
|
-
|
|
35
|
+
fixable: "code",
|
|
36
|
+
|
|
37
|
+
schema: [{
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: {
|
|
40
|
+
exceptions: {
|
|
41
|
+
type: "array",
|
|
42
|
+
items: { type: "string" },
|
|
43
|
+
uniqueItems: true
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
additionalProperties: false
|
|
47
|
+
}]
|
|
10
48
|
},
|
|
49
|
+
|
|
11
50
|
create(context) {
|
|
51
|
+
const options = context.options[0] || {};
|
|
52
|
+
const exceptions = options.exceptions || [];
|
|
53
|
+
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
54
|
+
|
|
55
|
+
const isTopLevelCallArgument = (identifier) => {
|
|
56
|
+
let scope = sourceCode.getScope(identifier);
|
|
57
|
+
let variable;
|
|
58
|
+
|
|
59
|
+
while (scope && !variable) {
|
|
60
|
+
variable = scope.set.get(identifier.name);
|
|
61
|
+
scope = scope.upper;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return variable?.references.some((reference) => {
|
|
65
|
+
const parent = reference.identifier.parent;
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
isCallOrConstruction(parent) &&
|
|
69
|
+
parent.arguments.includes(reference.identifier)
|
|
70
|
+
);
|
|
71
|
+
}) || false;
|
|
72
|
+
};
|
|
73
|
+
|
|
12
74
|
return {
|
|
13
75
|
VariableDeclaration(node) {
|
|
14
|
-
if (node.kind
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
76
|
+
if (node.kind !== "const") {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const isTopLevel = isTopLevelDeclaration(node);
|
|
81
|
+
const isExported = isExportedDeclaration(node);
|
|
82
|
+
const hasOnlyDestructuring = node.declarations.every((declaration) => {
|
|
83
|
+
return declaration.id.type !== "Identifier";
|
|
84
|
+
});
|
|
85
|
+
const canReplaceWithLet = node.declarations.every((declaration) => {
|
|
86
|
+
if (declaration.id.type !== "Identifier") {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const name = declaration.id.name;
|
|
91
|
+
const isValidConst =
|
|
92
|
+
exceptions.includes(name) ||
|
|
93
|
+
(
|
|
94
|
+
name.startsWith("__") &&
|
|
95
|
+
name.toUpperCase() === name
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
return name.startsWith("_") && !isValidConst;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!isTopLevel && !isExported && canReplaceWithLet) {
|
|
102
|
+
context.report({
|
|
103
|
+
node,
|
|
104
|
+
message: hasOnlyDestructuring
|
|
105
|
+
? "Local destructuring declarations should use \"let\"."
|
|
106
|
+
: "Local variables prefixed with \"_\" should use \"let\".",
|
|
107
|
+
fix(fixer) {
|
|
108
|
+
return fixer.replaceText(sourceCode.getFirstToken(node), "let");
|
|
21
109
|
}
|
|
22
110
|
});
|
|
111
|
+
|
|
112
|
+
return;
|
|
23
113
|
}
|
|
24
|
-
|
|
114
|
+
|
|
115
|
+
node.declarations.forEach((declaration) => {
|
|
116
|
+
if (declaration.id.type !== "Identifier") {
|
|
117
|
+
if (isTopLevel || isExported) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
context.report({
|
|
122
|
+
node: declaration,
|
|
123
|
+
message: "Local destructuring declarations should use \"let\"."
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const name = declaration.id.name;
|
|
130
|
+
const isUppercase = name.toUpperCase() === name;
|
|
131
|
+
const hasValidPlacement =
|
|
132
|
+
isTopLevel || name.startsWith("__");
|
|
133
|
+
const isTopLevelBuilder =
|
|
134
|
+
isTopLevel && isCallOrConstruction(declaration.init);
|
|
135
|
+
const isTopLevelBuilderArgument =
|
|
136
|
+
isTopLevel && isTopLevelCallArgument(declaration.id);
|
|
137
|
+
const isFunction = isFunctionExpression(declaration.init);
|
|
138
|
+
|
|
139
|
+
if (
|
|
140
|
+
isExported ||
|
|
141
|
+
isTopLevelBuilder ||
|
|
142
|
+
isTopLevelBuilderArgument ||
|
|
143
|
+
isFunction ||
|
|
144
|
+
exceptions.includes(name) ||
|
|
145
|
+
(isUppercase && hasValidPlacement)
|
|
146
|
+
) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
context.report({
|
|
151
|
+
node: declaration.id,
|
|
152
|
+
message:
|
|
153
|
+
"Consts should be uppercase and defined at the top level or be replaced with let _variable (Crisp convention)."
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
}
|
|
25
157
|
};
|
|
26
|
-
}
|
|
158
|
+
}
|
|
27
159
|
};
|
package/rules/variable-names.js
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
|
+
const isLocalDeclaration = (node) => {
|
|
2
|
+
let parent = node.parent;
|
|
3
|
+
|
|
4
|
+
while (parent) {
|
|
5
|
+
if (
|
|
6
|
+
parent.type === "ArrowFunctionExpression" ||
|
|
7
|
+
parent.type === "FunctionDeclaration" ||
|
|
8
|
+
parent.type === "FunctionExpression"
|
|
9
|
+
) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
parent = parent.parent;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return false;
|
|
17
|
+
};
|
|
18
|
+
|
|
1
19
|
export default {
|
|
2
20
|
meta: {
|
|
3
21
|
type: "suggestion",
|
|
4
22
|
docs: {
|
|
5
|
-
description: "
|
|
23
|
+
description: "Enforce that local variable names follow internal conventions.",
|
|
6
24
|
category: "Stylistic Issues",
|
|
7
25
|
recommended: false
|
|
8
26
|
},
|
|
@@ -24,110 +42,27 @@ export default {
|
|
|
24
42
|
const options = context.options[0] || {};
|
|
25
43
|
const variableExceptions = options.variableExceptions || [];
|
|
26
44
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
switch(node.type) {
|
|
33
|
-
// In case of a variable declaration, it checks if the variable name \
|
|
34
|
-
// starts with "_" or is an exception
|
|
35
|
-
case "VariableDeclaration": {
|
|
36
|
-
node.declarations.forEach((declaration) => {
|
|
37
|
-
if (declaration.id.name && !declaration.id.name.startsWith("_") && !variableExceptions.includes(declaration.id.name)) {
|
|
38
|
-
context.report({
|
|
39
|
-
node: declaration,
|
|
40
|
-
message: `Variables defined within a method should start with "_" ({${declaration.id.name}})`,
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// BlockStatement and Program nodes contain lists of statements \
|
|
49
|
-
// (node.body), so we check each statement
|
|
50
|
-
case "BlockStatement":
|
|
51
|
-
case "Program": {
|
|
52
|
-
node.body.forEach(checkDeclaration);
|
|
53
|
-
|
|
54
|
-
break;
|
|
45
|
+
return {
|
|
46
|
+
VariableDeclaration(node) {
|
|
47
|
+
// Constant names are validated by the const rule.
|
|
48
|
+
if (node.kind === "const" || !isLocalDeclaration(node)) {
|
|
49
|
+
return;
|
|
55
50
|
}
|
|
56
51
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
checkDeclaration(node.consequent);
|
|
61
|
-
|
|
62
|
-
if (node.alternate) {
|
|
63
|
-
checkDeclaration(node.alternate);
|
|
52
|
+
for (const declaration of node.declarations) {
|
|
53
|
+
if (declaration.id.type !== "Identifier") {
|
|
54
|
+
continue;
|
|
64
55
|
}
|
|
65
56
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
case "ForOfStatement": {
|
|
75
|
-
if (node.body) {
|
|
76
|
-
checkDeclaration(node.body);
|
|
57
|
+
if (
|
|
58
|
+
!declaration.id.name.startsWith("_") &&
|
|
59
|
+
!variableExceptions.includes(declaration.id.name)
|
|
60
|
+
) {
|
|
61
|
+
context.report({
|
|
62
|
+
node: declaration.id,
|
|
63
|
+
message: `Local variables should start with "_" ({${declaration.id.name}})`
|
|
64
|
+
});
|
|
77
65
|
}
|
|
78
|
-
|
|
79
|
-
// Check the loop condition itself
|
|
80
|
-
checkDeclaration(node.left);
|
|
81
|
-
|
|
82
|
-
break;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// SwitchStatement nodes contain an array of SwitchCase nodes that \
|
|
86
|
-
// represent each case in the switch statement
|
|
87
|
-
case "SwitchStatement": {
|
|
88
|
-
node.cases.forEach(checkDeclaration);
|
|
89
|
-
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// SwitchCase nodes contain a consequent which is an array of \
|
|
94
|
-
// Statements to be checked
|
|
95
|
-
case "SwitchCase": {
|
|
96
|
-
node.consequent.forEach(checkDeclaration);
|
|
97
|
-
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
default: {
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
// For each property in an object, if it's a function / arrow function, \
|
|
109
|
-
// check its body (usefull for Vue.js components, in which methods and \
|
|
110
|
-
// computed are defined as properties of a parent object)
|
|
111
|
-
Property(node) {
|
|
112
|
-
if (node.value.type === "FunctionExpression" ||
|
|
113
|
-
node.value.type === "ArrowFunctionExpression") {
|
|
114
|
-
checkDeclaration(node);
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
MethodDefinition(node) {
|
|
119
|
-
checkDeclaration(node.value.body);
|
|
120
|
-
},
|
|
121
|
-
|
|
122
|
-
ArrowFunctionExpression(node) {
|
|
123
|
-
if (node.body.type === "BlockStatement") {
|
|
124
|
-
checkDeclaration(node.body);
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
|
|
128
|
-
FunctionExpression(node) {
|
|
129
|
-
if (node.body.type === "BlockStatement") {
|
|
130
|
-
checkDeclaration(node.body);
|
|
131
66
|
}
|
|
132
67
|
}
|
|
133
68
|
};
|
|
@@ -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
|
+
}
|