@master/eslint-plugin-css 2.0.0-beta.184 → 2.0.0-beta.186
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/lib/index.js +0 -14
- package/lib/rules/class-order.js +41 -121
- package/lib/rules/class-validation.js +123 -0
- package/lib/utils/ast.js +218 -0
- package/lib/utils/resolve-context.js +21 -0
- package/package.json +1 -1
- package/tests/frameworks/react.js +55 -0
- package/tests/{class-order → frameworks}/svelte.js +10 -11
- package/tests/{class-order → frameworks}/vue.js +10 -11
- package/tests/order.js +66 -0
- package/tests/validation.js +69 -0
- package/lib/util/ast.js +0 -370
- package/lib/util/parser.js +0 -21
- package/lib/util/regex.js +0 -16
- package/lib/util/removeDuplicatesFromArray.js +0 -7
- package/lib/util/removeDuplicatesFromClassnamesAndWhitespaces.js +0 -18
- package/lib/util/settings.js +0 -39
- package/tests/class-order/core.js +0 -112
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
}
|
|
3
|
+
const rule = require('../../lib/rules/class-order')
|
|
4
|
+
const RuleTester = require('eslint').RuleTester
|
|
5
|
+
const ruleTester = new RuleTester({
|
|
6
|
+
parserOptions: {
|
|
7
|
+
ecmaFeatures: {
|
|
8
|
+
jsx: true,
|
|
9
|
+
},
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
12
|
|
|
13
|
-
var ruleTester = new RuleTester({ parserOptions })
|
|
14
13
|
ruleTester.run('svelte class order', rule, {
|
|
15
14
|
valid: [{ code: `<div class="bg:black fg:white f:24 m:8 p:8">Simple, basic</div>` }],
|
|
16
15
|
invalid: [
|
|
17
16
|
{
|
|
18
17
|
code: `<div class="m:8 bg:black p:8 fg:white f:24 {flipped ? 'flipped' : ''}">Enhancing readability</div>`,
|
|
19
18
|
output: `<div class="bg:black fg:white f:24 m:8 p:8 {flipped ? 'flipped' : ''}">Enhancing readability</div>`,
|
|
20
|
-
errors: [{ messageId: '
|
|
19
|
+
errors: [{ messageId: 'invalidClassOrder' }],
|
|
21
20
|
filename: 'test.svelte',
|
|
22
21
|
parser: require.resolve('svelte-eslint-parser'),
|
|
23
22
|
},
|
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
}
|
|
3
|
+
const rule = require('../../lib/rules/class-order')
|
|
4
|
+
const RuleTester = require('eslint').RuleTester
|
|
5
|
+
const ruleTester = new RuleTester({
|
|
6
|
+
parserOptions: {
|
|
7
|
+
ecmaFeatures: {
|
|
8
|
+
jsx: true,
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
12
|
|
|
13
|
-
var ruleTester = new RuleTester({ parserOptions })
|
|
14
13
|
ruleTester.run('vue class order', rule, {
|
|
15
14
|
valid: [{ code: `<div class="bg:black fg:white f:24 m:8 p:8">Simple, basic</div>` }],
|
|
16
15
|
invalid: [
|
|
17
16
|
{
|
|
18
17
|
code: `<template><div class="m:8 bg:black p:8 fg:white f:24">Enhancing readability</div></template>`,
|
|
19
18
|
output: `<template><div class="bg:black fg:white f:24 m:8 p:8">Enhancing readability</div></template>`,
|
|
20
|
-
errors: [{ messageId: '
|
|
19
|
+
errors: [{ messageId: 'invalidClassOrder' }],
|
|
21
20
|
filename: 'test.vue',
|
|
22
21
|
parser: require.resolve('vue-eslint-parser'),
|
|
23
22
|
},
|
package/tests/order.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const rule = require('../lib/rules/class-order')
|
|
4
|
+
const RuleTester = require('eslint').RuleTester
|
|
5
|
+
const ruleTester = new RuleTester({
|
|
6
|
+
parserOptions: {
|
|
7
|
+
ecmaFeatures: {
|
|
8
|
+
jsx: true,
|
|
9
|
+
},
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
ruleTester.run('class order', rule, {
|
|
14
|
+
valid: [
|
|
15
|
+
{
|
|
16
|
+
code: `<div class="bg:black fg:white f:24 m:8 p:8">Simple, basic</div>`,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
code: `<div class="card mt:20">Traditional class + syntax</div>`,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
code: `<div test="bg:black fg:white f:24 m:8 p:8">Simple, using 'test' prop</div>`,
|
|
23
|
+
options: [
|
|
24
|
+
{
|
|
25
|
+
classMatching: '^test$',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
invalid: [
|
|
31
|
+
{
|
|
32
|
+
code: `<div class="fg:white f:24 m:8 p:8 bg:black">Classnames will be ordered</div>`,
|
|
33
|
+
output: `<div class="bg:black fg:white f:24 m:8 p:8">Classnames will be ordered</div>`,
|
|
34
|
+
errors: [{ messageId: 'invalidClassOrder' }],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
code: `
|
|
38
|
+
<div class="
|
|
39
|
+
m:8
|
|
40
|
+
bg:black
|
|
41
|
+
p:8
|
|
42
|
+
fg:white
|
|
43
|
+
f:24
|
|
44
|
+
">
|
|
45
|
+
:)
|
|
46
|
+
</div>`,
|
|
47
|
+
output: `
|
|
48
|
+
<div class="
|
|
49
|
+
bg:black
|
|
50
|
+
fg:white
|
|
51
|
+
f:24
|
|
52
|
+
m:8
|
|
53
|
+
p:8
|
|
54
|
+
">
|
|
55
|
+
:)
|
|
56
|
+
</div>`,
|
|
57
|
+
errors: [{ messageId: 'invalidClassOrder' }],
|
|
58
|
+
parser: require.resolve('@angular-eslint/template-parser'),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
code: `<div class="flex uppercase m:0 m:0>li text-decoration:none>li>a px:4>li align-items:baseline fg:gray-30>li>a gap-x:28 font:12 font:semibold pb:6>li pt:20 pt:10>li {bb:3|solid|black}>li:has(>.router-link-active) {fg:black}>li:has(>.router-link-active)>a fg:gray-10>li>a:hover box-shadow:none>li>a:focus">Group</div>`,
|
|
62
|
+
output: `<div class="flex uppercase align-items:baseline box-shadow:none>li>a:focus fg:gray-30>li>a fg:gray-10>li>a:hover gap-x:28 font:12 font:semibold {bb:3|solid|black}>li:has(>.router-link-active) {fg:black}>li:has(>.router-link-active)>a m:0 m:0>li pb:6>li pt:20 pt:10>li px:4>li text-decoration:none>li>a">Group</div>`,
|
|
63
|
+
errors: [{ messageId: 'invalidClassOrder' }],
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const rule = require('../lib/rules/class-validation')
|
|
4
|
+
const RuleTester = require('eslint').RuleTester
|
|
5
|
+
|
|
6
|
+
new RuleTester({
|
|
7
|
+
parserOptions: {
|
|
8
|
+
ecmaFeatures: {
|
|
9
|
+
jsx: true,
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
}).run('invalid', rule, {
|
|
13
|
+
valid: [
|
|
14
|
+
{
|
|
15
|
+
code: `<div class="bg:black fg:white f:24 m:8 p:8">Simple, basic</div>`,
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
invalid: [
|
|
19
|
+
{
|
|
20
|
+
code: `<div class="bg:black m:mistake rrr">Simple, basic</div>`,
|
|
21
|
+
errors: [
|
|
22
|
+
{ messageId: 'invalidClass' },
|
|
23
|
+
{ messageId: 'disallowTraditionalClass' }
|
|
24
|
+
],
|
|
25
|
+
options: [
|
|
26
|
+
{
|
|
27
|
+
disallowTraditionalClass: true
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
code: `<div class="bg:black m:mistake rrr">Simple, basic</div>`,
|
|
33
|
+
errors: [
|
|
34
|
+
{ messageId: 'invalidClass' },
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
new RuleTester({
|
|
41
|
+
parserOptions: {
|
|
42
|
+
ecmaFeatures: {
|
|
43
|
+
jsx: true,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
settings: {
|
|
47
|
+
'@master/css': {
|
|
48
|
+
config: {
|
|
49
|
+
styles: { btn: 'block' }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}).run('invalid', rule, {
|
|
54
|
+
valid: [],
|
|
55
|
+
invalid: [
|
|
56
|
+
{
|
|
57
|
+
code: `<div class="bg:black m:mistake rrr btn">Simple, basic</div>`,
|
|
58
|
+
errors: [
|
|
59
|
+
{ messageId: 'invalidClass' },
|
|
60
|
+
{ messageId: 'disallowTraditionalClass' }
|
|
61
|
+
],
|
|
62
|
+
options: [
|
|
63
|
+
{
|
|
64
|
+
disallowTraditionalClass: true
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
})
|
package/lib/util/ast.js
DELETED
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Utility functions for AST
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
'use strict'
|
|
6
|
-
|
|
7
|
-
const { separatorRegEx } = require('./regex')
|
|
8
|
-
// context.parserPath
|
|
9
|
-
// /.../eslint-plugin-tailwindcss/node_modules/espree/espree.js
|
|
10
|
-
// /.../eslint-plugin-tailwindcss/node_modules/@angular-eslint/template-parser/dist/index.js
|
|
11
|
-
|
|
12
|
-
const removeDuplicatesFromArray = require('./removeDuplicatesFromArray')
|
|
13
|
-
|
|
14
|
-
function calleeToString(calleeNode) {
|
|
15
|
-
if (calleeNode.type === 'Identifier') {
|
|
16
|
-
return calleeNode.name
|
|
17
|
-
}
|
|
18
|
-
if (calleeNode.type === 'MemberExpression') {
|
|
19
|
-
return `${calleeNode.object.name}.${calleeNode.property.name}`
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Find out if node is `class` or `className`
|
|
25
|
-
*
|
|
26
|
-
* @param {ASTNode} node The AST node being checked
|
|
27
|
-
* @param {String} classRegex Regex to test the attribute that is being checked against
|
|
28
|
-
* @returns {Boolean}
|
|
29
|
-
*/
|
|
30
|
-
function isClassAttribute(node, classRegex) {
|
|
31
|
-
if (!node.name) {
|
|
32
|
-
return false
|
|
33
|
-
}
|
|
34
|
-
let name = ''
|
|
35
|
-
switch (node.type) {
|
|
36
|
-
case 'TextAttribute':
|
|
37
|
-
name = node.name
|
|
38
|
-
break
|
|
39
|
-
default:
|
|
40
|
-
name = node.name.name
|
|
41
|
-
}
|
|
42
|
-
return new RegExp(classRegex).test(name)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Find out if node is `class`
|
|
47
|
-
*
|
|
48
|
-
* @param {ASTNode} node The AST node being checked
|
|
49
|
-
* @param {String} classRegex Regex to test the attribute that is being checked against
|
|
50
|
-
* @returns {Boolean}
|
|
51
|
-
*/
|
|
52
|
-
function isVueClassAttribute(node, classRegex) {
|
|
53
|
-
const re = new RegExp(classRegex)
|
|
54
|
-
let name = ''
|
|
55
|
-
switch (true) {
|
|
56
|
-
case node.key && node.key.name && re.test(node.key.name):
|
|
57
|
-
// class="vue-classes-as-litteral"
|
|
58
|
-
return true
|
|
59
|
-
case node.key &&
|
|
60
|
-
node.key.name &&
|
|
61
|
-
node.key.name.name &&
|
|
62
|
-
node.key.argument &&
|
|
63
|
-
node.key.argument.name &&
|
|
64
|
-
/^bind$/.test(node.key.name.name) &&
|
|
65
|
-
re.test(node.key.argument.name):
|
|
66
|
-
// v-bind:class="vue-classes-as-bind"
|
|
67
|
-
// :class="vue-classes-as-bind"
|
|
68
|
-
return true
|
|
69
|
-
default:
|
|
70
|
-
return false
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Find out if node's value attribute is just simple text
|
|
76
|
-
*
|
|
77
|
-
* @param {ASTNode} node The AST node being checked
|
|
78
|
-
* @returns {Boolean}
|
|
79
|
-
*/
|
|
80
|
-
function isVLiteralValue(node) {
|
|
81
|
-
return node.value && node.value.type === 'VLiteral'
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Find out if node's value attribute is an ArrayExpression
|
|
86
|
-
*
|
|
87
|
-
* @param {ASTNode} node The AST node being checked
|
|
88
|
-
* @returns {Boolean}
|
|
89
|
-
*/
|
|
90
|
-
function isArrayExpression(node) {
|
|
91
|
-
return node.value && node.value.type === 'VExpressionContainer' && node.value.expression.type === 'ArrayExpression'
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Find out if node's value attribute is an ObjectExpression
|
|
96
|
-
*
|
|
97
|
-
* @param {ASTNode} node The AST node being checked
|
|
98
|
-
* @returns {Boolean}
|
|
99
|
-
*/
|
|
100
|
-
function isObjectExpression(node) {
|
|
101
|
-
return node.value && node.value.type === 'VExpressionContainer' && node.value.expression.type === 'ObjectExpression'
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Find out if node's value attribute is just simple text
|
|
106
|
-
*
|
|
107
|
-
* @param {ASTNode} node The AST node being checked
|
|
108
|
-
* @returns {Boolean}
|
|
109
|
-
*/
|
|
110
|
-
function isVueValidAttributeValue(node) {
|
|
111
|
-
switch (true) {
|
|
112
|
-
case isVLiteralValue(node): // Simple string
|
|
113
|
-
case isArrayExpression(node): // ['tw-unknown-class']
|
|
114
|
-
case isObjectExpression(node): // {'tw-unknown-class': true}
|
|
115
|
-
return true
|
|
116
|
-
default:
|
|
117
|
-
return false
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Find out if node's value attribute is just simple text
|
|
123
|
-
*
|
|
124
|
-
* @param {ASTNode} node The AST node being checked
|
|
125
|
-
* @returns {Boolean}
|
|
126
|
-
*/
|
|
127
|
-
function isLiteralAttributeValue(node) {
|
|
128
|
-
if (node.type === 'TextAttribute' && node.name === 'class' && typeof node.value === 'string') {
|
|
129
|
-
return true
|
|
130
|
-
}
|
|
131
|
-
if (node.value) {
|
|
132
|
-
switch (node.value.type) {
|
|
133
|
-
case 'Literal':
|
|
134
|
-
return true
|
|
135
|
-
case 'JSXExpressionContainer':
|
|
136
|
-
// className={"..."}
|
|
137
|
-
return node.value.expression.type === 'Literal'
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return false
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Find out if the node is a valid candidate for our rules
|
|
145
|
-
*
|
|
146
|
-
* @param {ASTNode} node The AST node being checked
|
|
147
|
-
* @param {String} classRegex Regex to test the attribute that is being checked against
|
|
148
|
-
* @returns {Boolean}
|
|
149
|
-
*/
|
|
150
|
-
function isValidJSXAttribute(node, classRegex) {
|
|
151
|
-
if (!isClassAttribute(node, classRegex)) {
|
|
152
|
-
// Only run for class[Name] attributes
|
|
153
|
-
return false
|
|
154
|
-
}
|
|
155
|
-
if (!isLiteralAttributeValue(node)) {
|
|
156
|
-
// No support for dynamic or conditional classnames
|
|
157
|
-
return false
|
|
158
|
-
}
|
|
159
|
-
return true
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Find out if the node is a valid candidate for our rules
|
|
164
|
-
*
|
|
165
|
-
* @param {ASTNode} node The AST node being checked
|
|
166
|
-
* @param {String} classRegex Regex to test the attribute that is being checked against
|
|
167
|
-
* @returns {Boolean}
|
|
168
|
-
*/
|
|
169
|
-
function isValidVueAttribute(node, classRegex) {
|
|
170
|
-
if (!isVueClassAttribute(node, classRegex)) {
|
|
171
|
-
// Only run for class attributes
|
|
172
|
-
return false
|
|
173
|
-
}
|
|
174
|
-
if (!isVueValidAttributeValue(node)) {
|
|
175
|
-
// No support for dynamic or conditional classnames
|
|
176
|
-
return false
|
|
177
|
-
}
|
|
178
|
-
return true
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function extractRangeFromNode(node) {
|
|
182
|
-
if (node.type === 'TextAttribute' && node.name === 'class') {
|
|
183
|
-
return [node.valueSpan.fullStart.offset, node.valueSpan.end.offset]
|
|
184
|
-
}
|
|
185
|
-
switch (node.value.type) {
|
|
186
|
-
case 'JSXExpressionContainer':
|
|
187
|
-
return node.value.expression.range
|
|
188
|
-
default:
|
|
189
|
-
return node.value.range
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function extractValueFromNode(node) {
|
|
194
|
-
if (node.type === 'TextAttribute' && node.name === 'class') {
|
|
195
|
-
return node.value
|
|
196
|
-
}
|
|
197
|
-
switch (node.value.type) {
|
|
198
|
-
case 'JSXExpressionContainer':
|
|
199
|
-
return node.value.expression.value
|
|
200
|
-
case 'VExpressionContainer':
|
|
201
|
-
switch (node.value.expression.type) {
|
|
202
|
-
case 'ArrayExpression':
|
|
203
|
-
return node.value.expression.elements
|
|
204
|
-
case 'ObjectExpression':
|
|
205
|
-
return node.value.expression.properties
|
|
206
|
-
}
|
|
207
|
-
return node.value.expression.value
|
|
208
|
-
default:
|
|
209
|
-
return node.value.value
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function extractClassnamesFromValue(classStr) {
|
|
214
|
-
if (typeof classStr !== 'string') {
|
|
215
|
-
return { classNames: [], whitespaces: [], headSpace: false, tailSpace: false }
|
|
216
|
-
}
|
|
217
|
-
let parts = classStr.split(separatorRegEx)
|
|
218
|
-
if (parts[0] === '') {
|
|
219
|
-
parts.shift()
|
|
220
|
-
}
|
|
221
|
-
if (parts[parts.length - 1] === '') {
|
|
222
|
-
parts.pop()
|
|
223
|
-
}
|
|
224
|
-
let headSpace = separatorRegEx.test(parts[0])
|
|
225
|
-
let tailSpace = separatorRegEx.test(parts[parts.length - 1])
|
|
226
|
-
const isClass = (_, i) => (headSpace ? i % 2 !== 0 : i % 2 === 0)
|
|
227
|
-
const isNotClass = (_, i) => (headSpace ? i % 2 === 0 : i % 2 !== 0)
|
|
228
|
-
let classNames = parts.filter(isClass)
|
|
229
|
-
let whitespaces = parts.filter(isNotClass)
|
|
230
|
-
return { classNames: classNames, whitespaces: whitespaces, headSpace: headSpace, tailSpace: tailSpace }
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Inspect and parse an abstract syntax node and run a callback function
|
|
235
|
-
*
|
|
236
|
-
* @param {ASTNode} rootNode The current root node being parsed by eslint
|
|
237
|
-
* @param {ASTNode} childNode The AST node child argument being checked
|
|
238
|
-
* @param {Function} cb The callback function
|
|
239
|
-
* @param {Boolean} skipConditional Optional, indicate distinct parsing for conditional nodes
|
|
240
|
-
* @param {Boolean} isolate Optional, set internally to isolate parsing and validation on conditional children
|
|
241
|
-
* @param {Array} ignoredKeys Optional, set object keys which should not be parsed e.g. for `cva`
|
|
242
|
-
* @returns {void}
|
|
243
|
-
*/
|
|
244
|
-
function parseNodeRecursive(rootNode, childNode, cb, skipConditional = false, isolate = false, ignoredKeys = []) {
|
|
245
|
-
// TODO allow vue non litteral
|
|
246
|
-
let originalClassNamesValue
|
|
247
|
-
let classNames
|
|
248
|
-
if (childNode === null) {
|
|
249
|
-
originalClassNamesValue = extractValueFromNode(rootNode);
|
|
250
|
-
({ classNames } = extractClassnamesFromValue(originalClassNamesValue))
|
|
251
|
-
classNames = removeDuplicatesFromArray(classNames)
|
|
252
|
-
if (classNames.length === 0) {
|
|
253
|
-
// Don't run for empty className
|
|
254
|
-
return
|
|
255
|
-
}
|
|
256
|
-
cb(classNames, rootNode)
|
|
257
|
-
} else if (childNode === undefined) {
|
|
258
|
-
// Ignore invalid child candidates (probably inside complex TemplateLiteral)
|
|
259
|
-
return
|
|
260
|
-
} else {
|
|
261
|
-
const forceIsolation = skipConditional ? true : isolate
|
|
262
|
-
let trim = false
|
|
263
|
-
switch (childNode.type) {
|
|
264
|
-
case 'TemplateLiteral':
|
|
265
|
-
childNode.expressions.forEach((exp) => {
|
|
266
|
-
parseNodeRecursive(rootNode, exp, cb, skipConditional, forceIsolation, ignoredKeys)
|
|
267
|
-
})
|
|
268
|
-
childNode.quasis.forEach((quasis) => {
|
|
269
|
-
parseNodeRecursive(rootNode, quasis, cb, skipConditional, isolate, ignoredKeys)
|
|
270
|
-
})
|
|
271
|
-
return
|
|
272
|
-
case 'ConditionalExpression':
|
|
273
|
-
parseNodeRecursive(rootNode, childNode.consequent, cb, skipConditional, forceIsolation, ignoredKeys)
|
|
274
|
-
parseNodeRecursive(rootNode, childNode.alternate, cb, skipConditional, forceIsolation, ignoredKeys)
|
|
275
|
-
return
|
|
276
|
-
case 'LogicalExpression':
|
|
277
|
-
parseNodeRecursive(rootNode, childNode.right, cb, skipConditional, forceIsolation, ignoredKeys)
|
|
278
|
-
return
|
|
279
|
-
case 'ArrayExpression':
|
|
280
|
-
childNode.elements.forEach((el) => {
|
|
281
|
-
parseNodeRecursive(rootNode, el, cb, skipConditional, forceIsolation, ignoredKeys)
|
|
282
|
-
})
|
|
283
|
-
return
|
|
284
|
-
case 'ObjectExpression':
|
|
285
|
-
childNode.properties.forEach((prop) => {
|
|
286
|
-
const isUsedByClassNamesPlugin = rootNode.callee && rootNode.callee.name === 'classnames'
|
|
287
|
-
|
|
288
|
-
if (prop.type === 'SpreadElement') {
|
|
289
|
-
// Ignore spread elements
|
|
290
|
-
return
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (prop.key.type === 'Identifier' && ignoredKeys.includes(prop.key.name)) {
|
|
294
|
-
// Ignore specific keys defined in settings
|
|
295
|
-
return
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
parseNodeRecursive(
|
|
299
|
-
rootNode,
|
|
300
|
-
isUsedByClassNamesPlugin ? prop.key : prop.value,
|
|
301
|
-
cb,
|
|
302
|
-
skipConditional,
|
|
303
|
-
forceIsolation,
|
|
304
|
-
ignoredKeys
|
|
305
|
-
)
|
|
306
|
-
})
|
|
307
|
-
return
|
|
308
|
-
case 'Property':
|
|
309
|
-
parseNodeRecursive(rootNode, childNode.key, cb, skipConditional, forceIsolation, ignoredKeys)
|
|
310
|
-
return
|
|
311
|
-
case 'Literal':
|
|
312
|
-
trim = true
|
|
313
|
-
originalClassNamesValue = childNode.value
|
|
314
|
-
break
|
|
315
|
-
case 'TemplateElement':
|
|
316
|
-
originalClassNamesValue = childNode.value.raw
|
|
317
|
-
break
|
|
318
|
-
}
|
|
319
|
-
({ classNames } = extractClassnamesFromValue(originalClassNamesValue))
|
|
320
|
-
classNames = removeDuplicatesFromArray(classNames)
|
|
321
|
-
if (classNames.length === 0) {
|
|
322
|
-
// Don't run for empty className
|
|
323
|
-
return
|
|
324
|
-
}
|
|
325
|
-
const targetNode = isolate ? null : rootNode
|
|
326
|
-
cb(classNames, targetNode)
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function getTemplateElementPrefix(text, raw) {
|
|
331
|
-
const idx = text.indexOf(raw)
|
|
332
|
-
if (idx === 0) {
|
|
333
|
-
return ''
|
|
334
|
-
}
|
|
335
|
-
return text.split(raw).shift()
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
function getTemplateElementSuffix(text, raw) {
|
|
339
|
-
if (text.indexOf(raw) === -1) {
|
|
340
|
-
return ''
|
|
341
|
-
}
|
|
342
|
-
return text.split(raw).pop()
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function getTemplateElementBody(text, prefix, suffix) {
|
|
346
|
-
let arr = text.split(prefix)
|
|
347
|
-
arr.shift()
|
|
348
|
-
let body = arr.join(prefix)
|
|
349
|
-
arr = body.split(suffix)
|
|
350
|
-
arr.pop()
|
|
351
|
-
return arr.join(suffix)
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
module.exports = {
|
|
355
|
-
calleeToString,
|
|
356
|
-
extractRangeFromNode,
|
|
357
|
-
extractValueFromNode,
|
|
358
|
-
extractClassnamesFromValue,
|
|
359
|
-
isClassAttribute,
|
|
360
|
-
isLiteralAttributeValue,
|
|
361
|
-
isArrayExpression,
|
|
362
|
-
isObjectExpression,
|
|
363
|
-
isValidJSXAttribute,
|
|
364
|
-
isValidVueAttribute,
|
|
365
|
-
isVLiteralValue,
|
|
366
|
-
parseNodeRecursive,
|
|
367
|
-
getTemplateElementPrefix,
|
|
368
|
-
getTemplateElementSuffix,
|
|
369
|
-
getTemplateElementBody,
|
|
370
|
-
}
|
package/lib/util/parser.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @see parserServices https://eslint.org/docs/developer-guide/working-with-rules#the-context-object
|
|
3
|
-
* @param {Object} context
|
|
4
|
-
* @param {Function} templateBodyVisitor
|
|
5
|
-
* @param {Function} scriptVisitor
|
|
6
|
-
* @returns
|
|
7
|
-
*/
|
|
8
|
-
function defineTemplateBodyVisitor(context, templateBodyVisitor, scriptVisitor) {
|
|
9
|
-
if (context.parserServices == null || context.parserServices.defineTemplateBodyVisitor == null) {
|
|
10
|
-
// Default parser
|
|
11
|
-
return scriptVisitor
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Using "vue-eslint-parser" requires this setup
|
|
15
|
-
// @see https://eslint.org/docs/developer-guide/working-with-rules#the-context-object
|
|
16
|
-
return context.parserServices.defineTemplateBodyVisitor(templateBodyVisitor, scriptVisitor)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
module.exports = {
|
|
20
|
-
defineTemplateBodyVisitor,
|
|
21
|
-
}
|
package/lib/util/regex.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Escapes a string to be used in a regular expression
|
|
3
|
-
* Copied from https://stackoverflow.com/a/3561711.
|
|
4
|
-
* @param {string} input
|
|
5
|
-
* @returns {string}
|
|
6
|
-
*/
|
|
7
|
-
function escapeRegex(input) {
|
|
8
|
-
return input.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&')
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const separatorRegEx = /([\t\n\f\r ]+)/
|
|
12
|
-
|
|
13
|
-
module.exports = {
|
|
14
|
-
escapeRegex,
|
|
15
|
-
separatorRegEx,
|
|
16
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
function removeDuplicatesFromClassnamesAndWhitespaces(orderedClassNames, whitespaces, headSpace, tailSpace) {
|
|
4
|
-
let previous = orderedClassNames[0]
|
|
5
|
-
const offset = (!headSpace && !tailSpace) || tailSpace ? -1 : 0
|
|
6
|
-
for (let i = 1; i < orderedClassNames.length; i++) {
|
|
7
|
-
const cls = orderedClassNames[i]
|
|
8
|
-
// This function assumes that the list of classNames is ordered, so just comparing to the previous className is enough
|
|
9
|
-
if (cls === previous) {
|
|
10
|
-
orderedClassNames.splice(i, 1)
|
|
11
|
-
whitespaces.splice(i + offset, 1)
|
|
12
|
-
i--
|
|
13
|
-
}
|
|
14
|
-
previous = cls
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
module.exports = removeDuplicatesFromClassnamesAndWhitespaces
|
package/lib/util/settings.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
const exploreConfig = require('explore-config').default
|
|
3
|
-
|
|
4
|
-
function getOption(context, name) {
|
|
5
|
-
// Options (defined at rule level)
|
|
6
|
-
const options = context.options[0] || {}
|
|
7
|
-
if (options[name] != undefined) {
|
|
8
|
-
return options[name]
|
|
9
|
-
}
|
|
10
|
-
// Settings (defined at plugin level, shared accross rules)
|
|
11
|
-
if (context.settings && context.settings.mastercss && context.settings.mastercss[name] != undefined) {
|
|
12
|
-
return context.settings.mastercss[name]
|
|
13
|
-
}
|
|
14
|
-
// Fallback to defaults
|
|
15
|
-
switch (name) {
|
|
16
|
-
case 'callees':
|
|
17
|
-
return ['classnames', 'clsx', 'ctl', 'cva']
|
|
18
|
-
case 'ignoredKeys':
|
|
19
|
-
return ['compoundVariants', 'defaultVariants']
|
|
20
|
-
case 'classRegex':
|
|
21
|
-
return '^class(Name)?$'
|
|
22
|
-
case 'config':
|
|
23
|
-
return exploreConfig('master.css.*', {})
|
|
24
|
-
case 'cssFiles':
|
|
25
|
-
return ['**/*.css', '!**/node_modules', '!**/.*', '!**/dist', '!**/build']
|
|
26
|
-
case 'cssFilesRefreshRate':
|
|
27
|
-
return 5_000
|
|
28
|
-
case 'removeDuplicates':
|
|
29
|
-
return true
|
|
30
|
-
case 'skipClassAttribute':
|
|
31
|
-
return false
|
|
32
|
-
case 'tags':
|
|
33
|
-
return []
|
|
34
|
-
case 'whitelist':
|
|
35
|
-
return []
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
module.exports = getOption
|