-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfilename-class-match.js
107 lines (91 loc) · 3.07 KB
/
filename-class-match.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
const stylelint = require("stylelint");
const path = require("path");
const ruleName = "crisp/filename-class-match";
const messages = stylelint.utils.ruleMessages(ruleName, {
expected: (className, fileName) => `Expected top-level class '${className}' to match file name '${fileName}'`
});
const ruleFunction = (primaryOption, secondaryOptions = {}, context) => {
return (root, result) => {
// Validate options for the rule
const validOptions = stylelint.utils.validateOptions(result, ruleName, {
actual: primaryOption,
possible: [true]
}, {
actual: secondaryOptions,
possible: {
prefix: [isString],
suffix: [isString]
},
optional: true
});
if (!validOptions) {
return;
}
// Skip non-Vue files
if (!root.source.input.file.endsWith(".vue")) {
return;
}
let fileScopeClass;
// Identify the parent selector
root.walkDecls(decl => {
if (decl.prop === "$c") {
// Remove leading `.` and `"`
fileScopeClass = decl.value.replace(/^["']?\./, "").replace(/["']?$/, "");
return;
}
});
// No parent selector found, this will be caught by
// `selector-class-interpolation`
if (!fileScopeClass) {
return;
}
const fileName = path.basename(root.source.input.file, ".vue");
const { prefix = "", suffix = "" } = secondaryOptions;
const expectedClassName = `${prefix}${toKebabCase(fileName)}${suffix}`;
// Skip `index`, `[*]` and `[[*]]` special filenames
if (fileName === "index" || fileName.startsWith("[")) {
return;
}
if (fileScopeClass !== expectedClassName) {
stylelint.utils.report({
ruleName,
result,
node: root,
message: messages.expected(fileScopeClass, expectedClassName)
});
}
};
};
// Convert a string to kebab-case
function toKebabCase(str) {
// Special kebab-case flavor:
// * `CheckFailures` gets converted to `check-failures`
// * `CheckSMTPFailures` gets converted to `check-smtp-failures`
// * `CheckIPsFailures` gets converted to `check-ips-failures`
// * `MonitoringMailerIPs` gets converted to `monitoring-mailer-ips`
return str
// Handle the transition from multiple uppercase letters to one or \
// multiple lowercase letters
.replace(/([A-Z]+)([A-Z]+[a-z]+|(?![a-z]))/g, (match, p1, p2) => {
// Only one lowercase letter?
if (p2.length === 2 && /[a-z]/.test(p2.slice(1))) {
// Make one group (this ensures `IPs` gets converted to `ips` \
// and not `i-ps`)
return `${p1}${p2}`;
}
// Make two different groups
return `${p1}-${p2}`;
})
// Insert a hyphen between lowercase to uppercase transitions
.replace(/([a-z])([A-Z])/g, "$1-$2")
// Replace spaces and multiple hyphens with a single hyphen
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
// Convert to lowercase
.toLowerCase();
}
// Check if a value is a string
function isString(value) {
return typeof value === "string";
}
module.exports = stylelint.createPlugin(ruleName, ruleFunction);