eslint-plugin-crisp 1.5.4 → 1.5.6
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 +4 -0
- package/index.js +8 -0
- package/package.json +1 -1
- package/recommended-vue.js +4 -0
- package/rules/vue-class-attribute-last.js +87 -0
- package/rules/vue-class-shorthand-max-len.js +71 -0
- package/rules/vue-no-component-class-shorthand.js +50 -0
- package/rules/vue-no-mixed-class.js +49 -0
package/README.md
CHANGED
|
@@ -220,12 +220,16 @@ Each item has emojis denoting:
|
|
|
220
220
|
| :- | :- | :- | :- | :- |
|
|
221
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 | | 🟢 |
|
|
222
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 | | 🟢 |
|
|
223
|
+
| [crisp/vue-class-attribute-last](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-class-attribute-last.js) | Enforces `class` to be the last attribute holding a value, and `:class` to be the last bound attribute | | 🟢 |
|
|
224
|
+
| [crisp/vue-class-shorthand-max-len](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-class-shorthand-max-len.js) | Requires a multiline `:class` array when Pug class shorthand exceeds 100 characters | | 🟢 |
|
|
223
225
|
| [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
226
|
| [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 | | 🟢 |
|
|
225
227
|
| [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 | | 🟢 |
|
|
226
228
|
| [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 | | 🟢 |
|
|
227
229
|
| [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) | | 🟢 |
|
|
228
230
|
| [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 | | 🟢 |
|
|
231
|
+
| [crisp/vue-no-component-class-shorthand](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-no-component-class-shorthand.js) | Disallows Pug class shorthand on Vue components | | 🟢 |
|
|
232
|
+
| [crisp/vue-no-mixed-class](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-no-mixed-class.js) | Disallows using both `class` and `:class` on the same element | | 🟢 |
|
|
229
233
|
| [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 | | 🟢 |
|
|
230
234
|
| [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 | | 🟢 |
|
|
231
235
|
| [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 | | 🟢 |
|
package/index.js
CHANGED
|
@@ -43,6 +43,8 @@ import ruleTwoLinesBetweenClassMembers from "./rules/two-lines-between-class-mem
|
|
|
43
43
|
import ruleVariableNames from "./rules/variable-names.js";
|
|
44
44
|
import ruleVueAttributeComma from "./rules/vue-attribute-comma.js";
|
|
45
45
|
import ruleVueAttributeLinebreak from "./rules/vue-attribute-linebreak.js";
|
|
46
|
+
import ruleVueClassAttributeLast from "./rules/vue-class-attribute-last.js";
|
|
47
|
+
import ruleVueClassShorthandMaxLen from "./rules/vue-class-shorthand-max-len.js";
|
|
46
48
|
import ruleVueComputedOrder from "./rules/vue-computed-order.js";
|
|
47
49
|
import ruleVueDataComment from "./rules/vue-data-comment.js";
|
|
48
50
|
import ruleVueDefineComponent from "./rules/vue-define-component.js";
|
|
@@ -52,6 +54,8 @@ import ruleVueHtmlIndent from "./rules/vue-html-indent.js";
|
|
|
52
54
|
import ruleVueHtmlQuotes from "./rules/vue-html-quotes.js";
|
|
53
55
|
import ruleVueMethodsSeparator from "./rules/vue-methods-separator.js";
|
|
54
56
|
import ruleVueNameProp from "./rules/vue-name-prop.js";
|
|
57
|
+
import ruleVueNoComponentClassShorthand from "./rules/vue-no-component-class-shorthand.js";
|
|
58
|
+
import ruleVueNoMixedClass from "./rules/vue-no-mixed-class.js";
|
|
55
59
|
import ruleVueNoNameOption from "./rules/vue-no-name-option.js";
|
|
56
60
|
import ruleVueNoRegexData from "./rules/vue-no-regex-data.js";
|
|
57
61
|
import ruleVuePropsDeclarationLineBreak from "./rules/vue-props-declaration-line-break.js";
|
|
@@ -115,6 +119,8 @@ const plugin = {
|
|
|
115
119
|
"variable-names": ruleVariableNames,
|
|
116
120
|
"vue-attribute-comma": ruleVueAttributeComma,
|
|
117
121
|
"vue-attribute-linebreak": ruleVueAttributeLinebreak,
|
|
122
|
+
"vue-class-attribute-last": ruleVueClassAttributeLast,
|
|
123
|
+
"vue-class-shorthand-max-len": ruleVueClassShorthandMaxLen,
|
|
118
124
|
"vue-computed-order": ruleVueComputedOrder,
|
|
119
125
|
"vue-data-comment": ruleVueDataComment,
|
|
120
126
|
"vue-define-component": ruleVueDefineComponent,
|
|
@@ -124,6 +130,8 @@ const plugin = {
|
|
|
124
130
|
"vue-html-quotes": ruleVueHtmlQuotes,
|
|
125
131
|
"vue-methods-separator": ruleVueMethodsSeparator,
|
|
126
132
|
"vue-name-prop": ruleVueNameProp,
|
|
133
|
+
"vue-no-component-class-shorthand": ruleVueNoComponentClassShorthand,
|
|
134
|
+
"vue-no-mixed-class": ruleVueNoMixedClass,
|
|
127
135
|
"vue-no-name-option": ruleVueNoNameOption,
|
|
128
136
|
"vue-no-regex-data": ruleVueNoRegexData,
|
|
129
137
|
"vue-props-declaration-line-break": ruleVuePropsDeclarationLineBreak,
|
package/package.json
CHANGED
package/recommended-vue.js
CHANGED
|
@@ -378,6 +378,8 @@ export default function configRecommendedVue(pluginCrisp) {
|
|
|
378
378
|
// Crisp Vue rules
|
|
379
379
|
"crisp/vue-attribute-comma": "error",
|
|
380
380
|
"crisp/vue-attribute-linebreak": "error",
|
|
381
|
+
"crisp/vue-class-attribute-last": "error",
|
|
382
|
+
"crisp/vue-class-shorthand-max-len": "error",
|
|
381
383
|
"crisp/vue-computed-order": "error",
|
|
382
384
|
"crisp/vue-data-comment": "error",
|
|
383
385
|
"crisp/vue-define-component": "error",
|
|
@@ -387,6 +389,8 @@ export default function configRecommendedVue(pluginCrisp) {
|
|
|
387
389
|
"crisp/vue-html-quotes": "error",
|
|
388
390
|
"crisp/vue-methods-separator": "error",
|
|
389
391
|
"crisp/vue-name-prop": "error",
|
|
392
|
+
"crisp/vue-no-component-class-shorthand": "error",
|
|
393
|
+
"crisp/vue-no-mixed-class": "error",
|
|
390
394
|
"crisp/vue-no-name-option": "error",
|
|
391
395
|
"crisp/vue-no-regex-data": "error",
|
|
392
396
|
"crisp/vue-props-declaration-line-break": "error",
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import utils from "eslint-plugin-vue/dist/utils/index.js";
|
|
2
|
+
|
|
3
|
+
// Checks whether an attribute is a static class attribute
|
|
4
|
+
function isStaticClass(attribute, sourceCode) {
|
|
5
|
+
if (attribute.directive === true || attribute.key.name !== "class") {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Ignore Pug class shorthands (eg. 'div.foo'), as they cannot be moved
|
|
10
|
+
return sourceCode.getText(attribute).startsWith("class");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Checks whether an attribute is a bound attribute (eg. ':prop' or 'v-bind:prop')
|
|
14
|
+
function isBoundAttribute(attribute) {
|
|
15
|
+
return (
|
|
16
|
+
attribute.directive === true &&
|
|
17
|
+
attribute.key.name.name === "bind" &&
|
|
18
|
+
attribute.key.argument !== null &&
|
|
19
|
+
attribute.key.argument.type === "VIdentifier"
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Checks whether an attribute is a bound class attribute
|
|
24
|
+
function isBoundClass(attribute) {
|
|
25
|
+
return isBoundAttribute(attribute) && attribute.key.argument.name === "class";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Checks whether an attribute holds a value (ie. not a boolean shorthand prop)
|
|
29
|
+
function hasValue(attribute) {
|
|
30
|
+
return attribute.value !== null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
meta: {
|
|
35
|
+
type: "layout",
|
|
36
|
+
docs: {
|
|
37
|
+
description: "enforce class attributes to be declared last within their group",
|
|
38
|
+
category: "Stylistic Issues",
|
|
39
|
+
recommended: false,
|
|
40
|
+
},
|
|
41
|
+
fixable: null,
|
|
42
|
+
schema: [], // no options
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
create(context) {
|
|
46
|
+
const sourceCode = context.getSourceCode();
|
|
47
|
+
|
|
48
|
+
// Reports attributes declared after the class attribute they should precede
|
|
49
|
+
function reportAttributesAfter(attributes, classIndex, className, isMisplaced) {
|
|
50
|
+
if (classIndex === -1) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
attributes.slice(classIndex + 1).forEach((attribute) => {
|
|
55
|
+
if (isMisplaced(attribute)) {
|
|
56
|
+
context.report({
|
|
57
|
+
node: attribute,
|
|
58
|
+
message: "Attribute \"{{attributeName}}\" must be declared before the \"{{className}}\" attribute.",
|
|
59
|
+
data: {
|
|
60
|
+
attributeName: sourceCode.getText(attribute.key),
|
|
61
|
+
className,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return utils.default.defineTemplateBodyVisitor(context, {
|
|
69
|
+
"VStartTag"(node) {
|
|
70
|
+
const boundClassIndex = node.attributes.findIndex(isBoundClass);
|
|
71
|
+
const staticClassIndex = node.attributes.findIndex((attribute) => {
|
|
72
|
+
return isStaticClass(attribute, sourceCode);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Bound class must come last among bound attributes
|
|
76
|
+
reportAttributesAfter(
|
|
77
|
+
node.attributes, boundClassIndex, ":class", isBoundAttribute
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// Static class must come last among attributes holding a value
|
|
81
|
+
reportAttributesAfter(
|
|
82
|
+
node.attributes, staticClassIndex, "class", hasValue
|
|
83
|
+
);
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import utils from "eslint-plugin-vue/dist/utils/index.js";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_MAX_LENGTH = 100;
|
|
4
|
+
const PUG_ELEMENT_HEAD_PATTERN =
|
|
5
|
+
/^(?:[A-Za-z][\w-]*)?(?:[.#][A-Za-z_][\w-]*)+$/;
|
|
6
|
+
const CLASS_SHORTHAND_PATTERN = /\.[A-Za-z_][\w-]*/;
|
|
7
|
+
|
|
8
|
+
// Checks whether a Pug element name belongs to a Vue component
|
|
9
|
+
function isComponent(node) {
|
|
10
|
+
return node.name.includes("-") || /^[A-Z]/.test(node.rawName);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default {
|
|
14
|
+
meta: {
|
|
15
|
+
type: "layout",
|
|
16
|
+
docs: {
|
|
17
|
+
description: "enforce a maximum length for Pug class shorthand",
|
|
18
|
+
category: "Stylistic Issues",
|
|
19
|
+
recommended: false,
|
|
20
|
+
},
|
|
21
|
+
fixable: null,
|
|
22
|
+
schema: [
|
|
23
|
+
{
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {
|
|
26
|
+
max: {
|
|
27
|
+
type: "integer",
|
|
28
|
+
minimum: 1,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
additionalProperties: false,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
messages: {
|
|
35
|
+
tooLong:
|
|
36
|
+
"Pug class shorthand is {{length}} characters long. Move classes to a multiline :class array (maximum: {{max}}).",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
create(context) {
|
|
41
|
+
const sourceCode = context.getSourceCode();
|
|
42
|
+
const maxLength = context.options[0]?.max ?? DEFAULT_MAX_LENGTH;
|
|
43
|
+
|
|
44
|
+
return utils.default.defineTemplateBodyVisitor(context, {
|
|
45
|
+
VElement(node) {
|
|
46
|
+
// Component shorthand is handled by vue-no-component-class-shorthand
|
|
47
|
+
if (isComponent(node)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const startTagText = sourceCode.getText(node.startTag);
|
|
52
|
+
const elementHead = startTagText.split(/[\s(]/, 1)[0];
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
PUG_ELEMENT_HEAD_PATTERN.test(elementHead) &&
|
|
56
|
+
CLASS_SHORTHAND_PATTERN.test(elementHead) &&
|
|
57
|
+
elementHead.length > maxLength
|
|
58
|
+
) {
|
|
59
|
+
context.report({
|
|
60
|
+
node: node.startTag,
|
|
61
|
+
messageId: "tooLong",
|
|
62
|
+
data: {
|
|
63
|
+
length: elementHead.length,
|
|
64
|
+
max: maxLength,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import utils from "eslint-plugin-vue/dist/utils/index.js";
|
|
2
|
+
|
|
3
|
+
const CLASS_SHORTHAND_PATTERN = /\.[A-Za-z_][\w-]*/;
|
|
4
|
+
|
|
5
|
+
// Checks whether a Pug element name belongs to a Vue component
|
|
6
|
+
function isComponent(node) {
|
|
7
|
+
return node.name.includes("-") || /^[A-Z]/.test(node.rawName);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
meta: {
|
|
12
|
+
type: "suggestion",
|
|
13
|
+
docs: {
|
|
14
|
+
description: "disallow Pug class shorthand on Vue components",
|
|
15
|
+
category: "Stylistic Issues",
|
|
16
|
+
recommended: false,
|
|
17
|
+
},
|
|
18
|
+
fixable: null,
|
|
19
|
+
schema: [], // no options
|
|
20
|
+
messages: {
|
|
21
|
+
useClassAttribute:
|
|
22
|
+
"Use a class attribute instead of Pug class shorthand on component \"{{name}}\".",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
create(context) {
|
|
27
|
+
const sourceCode = context.getSourceCode();
|
|
28
|
+
|
|
29
|
+
return utils.default.defineTemplateBodyVisitor(context, {
|
|
30
|
+
VElement(node) {
|
|
31
|
+
if (!isComponent(node)) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const startTagText = sourceCode.getText(node.startTag);
|
|
36
|
+
const elementHead = startTagText.split(/[\s(]/, 1)[0];
|
|
37
|
+
|
|
38
|
+
if (CLASS_SHORTHAND_PATTERN.test(elementHead)) {
|
|
39
|
+
context.report({
|
|
40
|
+
node: node.startTag,
|
|
41
|
+
messageId: "useClassAttribute",
|
|
42
|
+
data: {
|
|
43
|
+
name: node.rawName,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import utils from "eslint-plugin-vue/dist/utils/index.js";
|
|
2
|
+
|
|
3
|
+
// Checks whether an attribute is a static class attribute
|
|
4
|
+
function isStaticClass(attribute) {
|
|
5
|
+
return (
|
|
6
|
+
attribute.directive === false &&
|
|
7
|
+
attribute.key.name === "class"
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Checks whether an attribute is a bound class directive
|
|
12
|
+
function isBoundClass(attribute) {
|
|
13
|
+
return (
|
|
14
|
+
attribute.directive === true &&
|
|
15
|
+
attribute.key.name.name === "bind" &&
|
|
16
|
+
attribute.key.argument !== null &&
|
|
17
|
+
attribute.key.argument.type === "VIdentifier" &&
|
|
18
|
+
attribute.key.argument.name === "class"
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
meta: {
|
|
24
|
+
type: "problem",
|
|
25
|
+
docs: {
|
|
26
|
+
description: "disallow using both static and bound class attributes on the same element",
|
|
27
|
+
category: "Possible Errors",
|
|
28
|
+
recommended: false,
|
|
29
|
+
},
|
|
30
|
+
fixable: null,
|
|
31
|
+
schema: [], // no options
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
create(context) {
|
|
35
|
+
return utils.default.defineTemplateBodyVisitor(context, {
|
|
36
|
+
"VStartTag"(node) {
|
|
37
|
+
const staticClass = node.attributes.find(isStaticClass);
|
|
38
|
+
const boundClass = node.attributes.find(isBoundClass);
|
|
39
|
+
|
|
40
|
+
if (staticClass && boundClass) {
|
|
41
|
+
context.report({
|
|
42
|
+
node: boundClass,
|
|
43
|
+
message: "Cannot use both \"class\" and \":class\" on the same element, merge them into \":class\".",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
};
|