@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 CHANGED
@@ -1,19 +1,5 @@
1
- /**
2
- * @fileoverview Rules enforcing best practices while using Master CSS
3
- * @author Miles
4
- */
5
1
  'use strict'
6
2
 
7
- //------------------------------------------------------------------------------
8
- // Requirements
9
- //------------------------------------------------------------------------------
10
-
11
3
  const requireIndex = require('requireindex')
12
4
 
13
- //------------------------------------------------------------------------------
14
- // Plugin Definition
15
- //------------------------------------------------------------------------------
16
-
17
-
18
- // import all rules in lib/rules
19
5
  module.exports.rules = requireIndex(__dirname + '/rules')
@@ -1,27 +1,9 @@
1
- /* eslint-disable no-case-declarations */
2
- /**
3
- * @fileoverview Use a consistent orders for the Master CSS classnames, based on property then on variants
4
- * @author Miles
5
- */
6
1
  'use strict'
7
2
 
8
- // Modified from https://github.com/francoismassart/eslint-plugin-tailwindcss
9
-
10
- const astUtil = require('../util/ast')
11
- const removeDuplicatesFromClassnamesAndWhitespaces = require('../util/removeDuplicatesFromClassnamesAndWhitespaces')
12
- const getOption = require('../util/settings')
13
- const parserUtil = require('../util/parser')
14
-
3
+ const astUtil = require('../utils/ast')
4
+ const resolveContext = require('../utils/resolve-context')
15
5
  const { reorderForReadableClasses } = require('@master/css')
16
6
 
17
- //------------------------------------------------------------------------------
18
- // Rule Definition
19
- //------------------------------------------------------------------------------
20
-
21
- // Predefine message for use in context.report conditional.
22
- // messageId will still be usable in tests.
23
- const INVALID_CLASSNAMES_ORDER_MSG = 'No consistent class order followed.'
24
-
25
7
  module.exports = {
26
8
  meta: {
27
9
  docs: {
@@ -31,57 +13,12 @@ module.exports = {
31
13
  url: 'https://beta.css.master.co/docs/code-linting#enforce-a-consistent-and-logical-order-of-classes',
32
14
  },
33
15
  messages: {
34
- invalidOrder: INVALID_CLASSNAMES_ORDER_MSG,
16
+ invalidClassOrder: 'No consistent class order followed.',
35
17
  },
36
- fixable: 'code',
37
- schema: [
38
- {
39
- type: 'object',
40
- properties: {
41
- callees: {
42
- type: 'array',
43
- items: { type: 'string', minLength: 0 },
44
- uniqueItems: true,
45
- },
46
- ignoredKeys: {
47
- type: 'array',
48
- items: { type: 'string', minLength: 0 },
49
- uniqueItems: true,
50
- },
51
- config: {
52
- // returned from `loadConfig()` utility
53
- type: ['string', 'object'],
54
- },
55
- removeDuplicates: {
56
- // default: true,
57
- type: 'boolean',
58
- },
59
- tags: {
60
- type: 'array',
61
- items: { type: 'string', minLength: 0 },
62
- uniqueItems: true,
63
- },
64
- },
65
- },
66
- ],
18
+ fixable: 'code'
67
19
  },
68
-
69
20
  create: function (context) {
70
- const callees = getOption(context, 'callees')
71
- const skipClassAttribute = getOption(context, 'skipClassAttribute')
72
- const tags = getOption(context, 'tags')
73
- const masterCssConfig = getOption(context, 'config')
74
- const classRegex = getOption(context, 'classRegex')
75
-
76
- //----------------------------------------------------------------------
77
- // Helpers
78
- //----------------------------------------------------------------------
79
- /**
80
- * Recursive function crawling into child nodes
81
- * @param {ASTNode} node The root node of the current parsing
82
- * @param {ASTNode} arg The child node of node
83
- * @returns {void}
84
- */
21
+ const { options, settings, config } = resolveContext(context)
85
22
  const sortNodeArgumentValue = (node, arg = null) => {
86
23
  let originalClassNamesValue = null
87
24
  let start = null
@@ -145,7 +82,7 @@ module.exports = {
145
82
  }
146
83
  start = arg.range[0]
147
84
  end = arg.range[1]
148
- break;
85
+ break
149
86
  case 'TemplateElement':
150
87
  originalClassNamesValue = arg.value.raw
151
88
  if (originalClassNamesValue === '') {
@@ -173,12 +110,12 @@ module.exports = {
173
110
  return
174
111
  }
175
112
 
176
- let orderedClassNames = reorderForReadableClasses(classNames, masterCssConfig)
113
+ let orderedClassNames = reorderForReadableClasses(classNames, config)
177
114
  .filter(eachOrderedClassName => classNames.includes(eachOrderedClassName))
178
115
 
179
- orderedClassNames = orderedClassNames.concat(classNames.filter(x => !orderedClassNames.includes(x)))
180
-
181
- removeDuplicatesFromClassnamesAndWhitespaces(orderedClassNames, whitespaces, headSpace, tailSpace)
116
+ orderedClassNames = classNames.filter(x => !orderedClassNames.includes(x))
117
+ .concat(orderedClassNames)
118
+ .filter(x => x.trim() !== '')
182
119
 
183
120
  // Generates the validated/sorted attribute value
184
121
  let validatedClassNamesValue = ''
@@ -195,7 +132,7 @@ module.exports = {
195
132
  validatedClassNamesValue = prefix + validatedClassNamesValue + suffix
196
133
  context.report({
197
134
  node: node,
198
- messageId: 'invalidOrder',
135
+ messageId: 'invalidClassOrder',
199
136
  fix: function (fixer) {
200
137
  return fixer.replaceTextRange([start, end], validatedClassNamesValue)
201
138
  },
@@ -203,24 +140,9 @@ module.exports = {
203
140
  }
204
141
  }
205
142
 
206
- //----------------------------------------------------------------------
207
- // Public
208
- //----------------------------------------------------------------------
209
-
210
- const attributeVisitor = function (node) {
211
- if (!astUtil.isClassAttribute(node, classRegex) || skipClassAttribute) {
212
- return
213
- }
214
- if (astUtil.isLiteralAttributeValue(node)) {
215
- sortNodeArgumentValue(node)
216
- } else if (node.value && node.value.type === 'JSXExpressionContainer') {
217
- sortNodeArgumentValue(node, node.value.expression)
218
- }
219
- }
220
-
221
143
  const callExpressionVisitor = function (node) {
222
144
  const calleeStr = astUtil.calleeToString(node.callee)
223
- if (callees.findIndex((name) => calleeStr === name) === -1) {
145
+ if (settings.callees.findIndex((name) => calleeStr === name) === -1) {
224
146
  return
225
147
  }
226
148
 
@@ -230,53 +152,51 @@ module.exports = {
230
152
  }
231
153
 
232
154
  const scriptVisitor = {
233
- JSXAttribute: attributeVisitor,
234
- SvelteAttribute: function (node) {
235
- if (!node.key?.name) return
236
- if (!new RegExp(classRegex).test(node.key.name) || skipClassAttribute) {
237
- return
155
+ CallExpression: callExpressionVisitor,
156
+ JSXAttribute: function (node) {
157
+ if (!node.name || !new RegExp(settings.classMatching).test(node.name.name)) return
158
+ if (node.value && node.value.type === 'Literal') {
159
+ sortNodeArgumentValue(node)
160
+ } else if (node.value && node.value.type === 'JSXExpressionContainer') {
161
+ sortNodeArgumentValue(node, node.value.expression)
238
162
  }
163
+ },
164
+ SvelteAttribute: function (node) {
165
+ if (!node.key?.name || !new RegExp(settings.classMatching).test(node.key.name)) return
239
166
  for (const eachValue of node.value) {
240
167
  sortNodeArgumentValue(node, eachValue)
241
168
  }
242
169
  },
243
- TextAttribute: attributeVisitor,
244
- CallExpression: callExpressionVisitor,
170
+ TextAttribute: function (node) {
171
+ if (!node.name || !new RegExp(settings.classMatching).test(node.name)) return
172
+ sortNodeArgumentValue(node)
173
+ },
245
174
  TaggedTemplateExpression: function (node) {
246
- if (!tags.includes(node.tag.name)) {
175
+ if (!settings.tags.includes(node.tag.name)) {
247
176
  return
248
177
  }
249
-
250
178
  sortNodeArgumentValue(node, node.quasi)
251
179
  },
252
180
  }
253
- const templateVisitor = {
181
+ const templateBodyVisitor = {
254
182
  CallExpression: callExpressionVisitor,
255
- /*
256
- Tagged templates inside data bindings
257
- https://github.com/vuejs/vue/issues/9721
258
- */
259
183
  VAttribute: function (node) {
260
- switch (true) {
261
- case !astUtil.isValidVueAttribute(node, classRegex):
262
- return
263
- case astUtil.isVLiteralValue(node):
264
- sortNodeArgumentValue(node, null)
265
- break
266
- case astUtil.isArrayExpression(node):
267
- node.value.expression.elements.forEach((arg) => {
268
- sortNodeArgumentValue(node, arg)
269
- })
270
- break
271
- case astUtil.isObjectExpression(node):
272
- node.value.expression.properties.forEach((prop) => {
273
- sortNodeArgumentValue(node, prop)
274
- })
275
- break
184
+ if (node.value && node.value.type === 'VLiteral') {
185
+ sortNodeArgumentValue(node)
186
+ } else if (node.value && node.value.type === 'VExpressionContainer' && node.value.expression.type === 'ArrayExpression') {
187
+ node.value.expression.elements.forEach((arg) => {
188
+ sortNodeArgumentValue(node, arg)
189
+ })
190
+ } else if (node.value && node.value.type === 'VExpressionContainer' && node.value.expression.type === 'ObjectExpression') {
191
+ sortNodeArgumentValue(node, prop)
276
192
  }
277
193
  },
278
194
  }
279
195
 
280
- return parserUtil.defineTemplateBodyVisitor(context, templateVisitor, scriptVisitor)
196
+ if (context.parserServices == null || context.parserServices.defineTemplateBodyVisitor == null) {
197
+ return scriptVisitor
198
+ } else {
199
+ return context.parserServices.defineTemplateBodyVisitor(templateBodyVisitor, scriptVisitor)
200
+ }
281
201
  },
282
202
  }
@@ -0,0 +1,123 @@
1
+ 'use strict'
2
+
3
+ const astUtil = require('../utils/ast')
4
+ const resolveContext = require('../utils/resolve-context')
5
+ const { validate } = require('@master/css-validator')
6
+
7
+ module.exports = {
8
+ meta: {
9
+ docs: {
10
+ description: 'Check the validity of classes with your configuration',
11
+ category: 'Stylistic Issues',
12
+ recommended: false,
13
+ url: 'https://beta.css.master.co/docs/code-linting#check-the-validity-of-classes-with-your-configuration',
14
+ },
15
+ messages: {
16
+ invalidClass: '{{message}}',
17
+ disallowTraditionalClass: '{{message}}',
18
+ },
19
+ fixable: null
20
+ },
21
+ create: function (context) {
22
+ const { options, settings, config } = resolveContext(context)
23
+ const checkNodeArgumentValue = (node, arg = null) => {
24
+ astUtil.parseNodeRecursive(
25
+ node,
26
+ arg,
27
+ (classNames, node) => {
28
+ const sourceCode = context.getSourceCode()
29
+ const sourceCodeLines = sourceCode.lines
30
+ const nodeStartLine = node.loc.start.line
31
+ const nodeEndLine = node.loc.end.line
32
+ for (const className of classNames) {
33
+ const { isMasterCSS, errors } = validate(className, { config })
34
+ if (errors.length > 0) {
35
+ for (const error of errors) {
36
+ if (isMasterCSS) {
37
+ context.report({
38
+ loc: astUtil.findLoc(className, sourceCodeLines, nodeStartLine, nodeEndLine),
39
+ messageId: 'invalidClass',
40
+ data: {
41
+ message: error.message + '.',
42
+ }
43
+ })
44
+ } else if (!isMasterCSS && options.disallowTraditionalClass) {
45
+ context.report({
46
+ loc: astUtil.findLoc(className, sourceCodeLines, nodeStartLine, nodeEndLine),
47
+ messageId: 'disallowTraditionalClass',
48
+ data: {
49
+ message: `Disallow a traditional class \`${className}\`.`,
50
+ }
51
+ })
52
+ }
53
+ }
54
+ }
55
+ }
56
+ },
57
+ false,
58
+ false,
59
+ settings.ignoredKeys
60
+ )
61
+ }
62
+
63
+ const callExpressionVisitor = function (node) {
64
+ const calleeStr = astUtil.calleeToString(node.callee)
65
+ if (settings.callees.findIndex((name) => calleeStr === name) === -1) {
66
+ return
67
+ }
68
+
69
+ node.arguments.forEach((arg) => {
70
+ checkNodeArgumentValue(node, arg)
71
+ })
72
+ }
73
+
74
+ const scriptVisitor = {
75
+ CallExpression: callExpressionVisitor,
76
+ JSXAttribute: function (node) {
77
+ if (!node.name || !new RegExp(settings.classMatching).test(node.name.name)) return
78
+ if (node.value && node.value.type === 'Literal') {
79
+ checkNodeArgumentValue(node)
80
+ } else if (node.value && node.value.type === 'JSXExpressionContainer') {
81
+ checkNodeArgumentValue(node, node.value.expression)
82
+ }
83
+ },
84
+ SvelteAttribute: function (node) {
85
+ if (!node.key?.name || !new RegExp(settings.classMatching).test(node.key.name)) return
86
+ for (const eachValue of node.value) {
87
+ checkNodeArgumentValue(node, eachValue)
88
+ }
89
+ },
90
+ TextAttribute: function (node) {
91
+ if (!node.name || !new RegExp(settings.classMatching).test(node.name)) return
92
+ checkNodeArgumentValue(node)
93
+ },
94
+ TaggedTemplateExpression: function (node) {
95
+ if (!settings.tags.includes(node.tag.name)) {
96
+ return
97
+ }
98
+ checkNodeArgumentValue(node, node.quasi)
99
+ },
100
+ }
101
+
102
+ const templateBodyVisitor = {
103
+ CallExpression: callExpressionVisitor,
104
+ VAttribute: function (node) {
105
+ if (node.value && node.value.type === 'VLiteral') {
106
+ checkNodeArgumentValue(node)
107
+ } else if (node.value && node.value.type === 'VExpressionContainer' && node.value.expression.type === 'ArrayExpression') {
108
+ node.value.expression.elements.forEach((arg) => {
109
+ checkNodeArgumentValue(node, arg)
110
+ })
111
+ } else if (node.value && node.value.type === 'VExpressionContainer' && node.value.expression.type === 'ObjectExpression') {
112
+ checkNodeArgumentValue(node, prop)
113
+ }
114
+ },
115
+ }
116
+
117
+ if (context.parserServices == null || context.parserServices.defineTemplateBodyVisitor == null) {
118
+ return scriptVisitor
119
+ } else {
120
+ return context.parserServices.defineTemplateBodyVisitor(templateBodyVisitor, scriptVisitor)
121
+ }
122
+ },
123
+ }
@@ -0,0 +1,218 @@
1
+ 'use strict'
2
+
3
+ const separatorRegEx = /([\t\n\f\r ]+)/
4
+
5
+ function calleeToString(calleeNode) {
6
+ if (calleeNode.type === 'Identifier') {
7
+ return calleeNode.name
8
+ }
9
+ if (calleeNode.type === 'MemberExpression') {
10
+ return `${calleeNode.object.name}.${calleeNode.property.name}`
11
+ }
12
+ }
13
+
14
+ function extractRangeFromNode(node) {
15
+ if (node.type === 'TextAttribute' && node.name === 'class') {
16
+ return [node.valueSpan.fullStart.offset, node.valueSpan.end.offset]
17
+ }
18
+ switch (node.value.type) {
19
+ case 'JSXExpressionContainer':
20
+ return node.value.expression.range
21
+ default:
22
+ return node.value.range
23
+ }
24
+ }
25
+
26
+ function extractValueFromNode(node) {
27
+ if (node.type === 'TextAttribute' && node.name === 'class') {
28
+ return node.value
29
+ }
30
+ switch (node.value.type) {
31
+ case 'JSXExpressionContainer':
32
+ return node.value.expression.value
33
+ case 'VExpressionContainer':
34
+ switch (node.value.expression.type) {
35
+ case 'ArrayExpression':
36
+ return node.value.expression.elements
37
+ case 'ObjectExpression':
38
+ return node.value.expression.properties
39
+ }
40
+ return node.value.expression.value
41
+ default:
42
+ return node.value.value
43
+ }
44
+ }
45
+
46
+ function extractClassnamesFromValue(classStr) {
47
+ if (typeof classStr !== 'string') {
48
+ return { classNames: [], whitespaces: [], headSpace: false, tailSpace: false }
49
+ }
50
+ let parts = classStr.split(separatorRegEx)
51
+ if (parts[0] === '') {
52
+ parts.shift()
53
+ }
54
+ if (parts[parts.length - 1] === '') {
55
+ parts.pop()
56
+ }
57
+ let headSpace = separatorRegEx.test(parts[0])
58
+ let tailSpace = separatorRegEx.test(parts[parts.length - 1])
59
+ const isClass = (_, i) => (headSpace ? i % 2 !== 0 : i % 2 === 0)
60
+ const isNotClass = (_, i) => (headSpace ? i % 2 === 0 : i % 2 !== 0)
61
+ let classNames = parts.filter(isClass)
62
+ let whitespaces = parts.filter(isNotClass)
63
+ return { classNames: classNames, whitespaces: whitespaces, headSpace: headSpace, tailSpace: tailSpace }
64
+ }
65
+
66
+
67
+ /**
68
+ * Inspect and parse an abstract syntax node and run a callback function
69
+ *
70
+ * @param {ASTNode} rootNode The current root node being parsed by eslint
71
+ * @param {ASTNode} childNode The AST node child argument being checked
72
+ * @param {Function} cb The callback function
73
+ * @param {Boolean} skipConditional Optional, indicate distinct parsing for conditional nodes
74
+ * @param {Boolean} isolate Optional, set internally to isolate parsing and validation on conditional children
75
+ * @param {Array} ignoredKeys Optional, set object keys which should not be parsed e.g. for `cva`
76
+ * @returns {void}
77
+ */
78
+ function parseNodeRecursive(rootNode, childNode, cb, skipConditional = false, isolate = false, ignoredKeys = []) {
79
+ // TODO allow vue non litteral
80
+ let originalClassNamesValue
81
+ let classNames
82
+ if (childNode === null) {
83
+ originalClassNamesValue = extractValueFromNode(rootNode);
84
+ ({ classNames } = extractClassnamesFromValue(originalClassNamesValue))
85
+ classNames = [... new Set(classNames)]
86
+ if (classNames.length === 0) {
87
+ // Don't run for empty className
88
+ return
89
+ }
90
+ cb(classNames, rootNode)
91
+ } else if (childNode === undefined) {
92
+ // Ignore invalid child candidates (probably inside complex TemplateLiteral)
93
+ return
94
+ } else {
95
+ const forceIsolation = skipConditional ? true : isolate
96
+ let trim = false
97
+ switch (childNode.type) {
98
+ case 'TemplateLiteral':
99
+ childNode.expressions.forEach((exp) => {
100
+ parseNodeRecursive(rootNode, exp, cb, skipConditional, forceIsolation, ignoredKeys)
101
+ })
102
+ childNode.quasis.forEach((quasis) => {
103
+ parseNodeRecursive(rootNode, quasis, cb, skipConditional, isolate, ignoredKeys)
104
+ })
105
+ return
106
+ case 'ConditionalExpression':
107
+ parseNodeRecursive(rootNode, childNode.consequent, cb, skipConditional, forceIsolation, ignoredKeys)
108
+ parseNodeRecursive(rootNode, childNode.alternate, cb, skipConditional, forceIsolation, ignoredKeys)
109
+ return
110
+ case 'LogicalExpression':
111
+ parseNodeRecursive(rootNode, childNode.right, cb, skipConditional, forceIsolation, ignoredKeys)
112
+ return
113
+ case 'ArrayExpression':
114
+ childNode.elements.forEach((el) => {
115
+ parseNodeRecursive(rootNode, el, cb, skipConditional, forceIsolation, ignoredKeys)
116
+ })
117
+ return
118
+ case 'ObjectExpression':
119
+ childNode.properties.forEach((prop) => {
120
+ const isUsedByClassNamesPlugin = rootNode.callee && rootNode.callee.name === 'classnames'
121
+
122
+ if (prop.type === 'SpreadElement') {
123
+ // Ignore spread elements
124
+ return
125
+ }
126
+
127
+ if (prop.key.type === 'Identifier' && ignoredKeys.includes(prop.key.name)) {
128
+ // Ignore specific keys defined in settings
129
+ return
130
+ }
131
+
132
+ parseNodeRecursive(
133
+ rootNode,
134
+ isUsedByClassNamesPlugin ? prop.key : prop.value,
135
+ cb,
136
+ skipConditional,
137
+ forceIsolation,
138
+ ignoredKeys
139
+ )
140
+ })
141
+ return
142
+ case 'Property':
143
+ parseNodeRecursive(rootNode, childNode.key, cb, skipConditional, forceIsolation, ignoredKeys)
144
+ return
145
+ case 'Literal':
146
+ trim = true
147
+ originalClassNamesValue = childNode.value
148
+ break
149
+ case 'TemplateElement':
150
+ originalClassNamesValue = childNode.value.raw
151
+ break
152
+ }
153
+ ({ classNames } = extractClassnamesFromValue(originalClassNamesValue))
154
+ classNames = removeDuplicatesFromArray(classNames)
155
+ if (classNames.length === 0) {
156
+ // Don't run for empty className
157
+ return
158
+ }
159
+ const targetNode = isolate ? null : rootNode
160
+ cb(classNames, targetNode)
161
+ }
162
+ }
163
+
164
+ function getTemplateElementPrefix(text, raw) {
165
+ const idx = text.indexOf(raw)
166
+ if (idx === 0) {
167
+ return ''
168
+ }
169
+ return text.split(raw).shift()
170
+ }
171
+
172
+ function getTemplateElementSuffix(text, raw) {
173
+ if (text.indexOf(raw) === -1) {
174
+ return ''
175
+ }
176
+ return text.split(raw).pop()
177
+ }
178
+
179
+ function getTemplateElementBody(text, prefix, suffix) {
180
+ let arr = text.split(prefix)
181
+ arr.shift()
182
+ let body = arr.join(prefix)
183
+ arr = body.split(suffix)
184
+ arr.pop()
185
+ return arr.join(suffix)
186
+ }
187
+
188
+ function findLoc(text, lines, startLine, endLine) {
189
+ for (let i = startLine; i <= endLine; i++) {
190
+ const sourceCodeLine = lines[i - 1]
191
+ const index = sourceCodeLine.indexOf(text)
192
+ if (index !== -1) {
193
+ return {
194
+ start: {
195
+ line: i,
196
+ column: index
197
+ },
198
+ end: {
199
+ line: i,
200
+ column: index + text.length
201
+ }
202
+ }
203
+ }
204
+ }
205
+ return null
206
+ }
207
+
208
+ module.exports = {
209
+ calleeToString,
210
+ extractRangeFromNode,
211
+ extractValueFromNode,
212
+ extractClassnamesFromValue,
213
+ getTemplateElementPrefix,
214
+ getTemplateElementSuffix,
215
+ getTemplateElementBody,
216
+ parseNodeRecursive,
217
+ findLoc
218
+ }
@@ -0,0 +1,21 @@
1
+ 'use strict'
2
+
3
+ const exploreConfig = require('explore-config').default
4
+
5
+ const resolveContext = function (context) {
6
+ const settings = Object.assign({
7
+ callees: ['classnames', 'clsx', 'ctl', 'cva', 'cv', 'classVariant', 'styled'],
8
+ ignoredKeys: ['compoundVariants', 'defaultVariants'],
9
+ classMatching: '^class(Name)?$',
10
+ config: 'master.css.*',
11
+ tags: []
12
+ }, context.settings?.['@master/css'])
13
+ const config = settings?.config
14
+ return {
15
+ settings,
16
+ options: context.options[0] || {},
17
+ config: typeof config === 'object' ? config : exploreConfig(settings?.config || '')
18
+ }
19
+ }
20
+
21
+ module.exports = resolveContext
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@master/eslint-plugin-css","scripts":{"test":"mocha tests --recursive --timeout 10000","test:file":"mocha tests"},"keywords":["mastercss","eslint","eslintplugin"],"license":"MIT","description":"Master CSS ESLint Plugin","author":"Aoyue Design LLC.","funding":"https://css.master.co/docs/donate","homepage":"https://css.master.co","bugs":{"url":"https://github.com/master-co/css/issues"},"repository":{"type":"git","url":"https://github.com/master-co/css.git","directory":"packages/eslint-plugin"},"sideEffects":false,"main":"./lib/index.js","exports":"./lib/index.js","publishConfig":{"access":"public"},"dependencies":{"@master/css":"^2.0.0-beta.184","explore-config":"^2.2.10","requireindex":"^1.2.0"},"devDependencies":{"@angular-eslint/template-parser":"^16.2.0","@types/node":"^20.6.0","@typescript-eslint/parser":"^5.50.0","eslint":"^7.1.0","mocha":"^10.2.0","svelte-eslint-parser":"^0.33.1","vue-eslint-parser":"^9.3.1"},"version":"2.0.0-beta.184"}
1
+ {"name":"@master/eslint-plugin-css","scripts":{"test":"mocha tests --recursive --timeout 10000","test:file":"mocha tests"},"keywords":["mastercss","eslint","eslintplugin"],"license":"MIT","description":"Master CSS ESLint Plugin","author":"Aoyue Design LLC.","funding":"https://css.master.co/docs/donate","homepage":"https://css.master.co","bugs":{"url":"https://github.com/master-co/css/issues"},"repository":{"type":"git","url":"https://github.com/master-co/css.git","directory":"packages/eslint-plugin"},"sideEffects":false,"main":"./lib/index.js","exports":"./lib/index.js","publishConfig":{"access":"public"},"dependencies":{"@master/css":"^2.0.0-beta.186","@master/css-validator":"^2.0.0-beta.186","explore-config":"^2.2.10","requireindex":"^1.2.0"},"devDependencies":{"@angular-eslint/template-parser":"^16.2.0","@types/node":"^20.6.0","@typescript-eslint/parser":"^5.50.0","eslint":"^7.1.0","mocha":"^10.2.0","svelte-eslint-parser":"^0.33.1","vue-eslint-parser":"^9.3.1"},"version":"2.0.0-beta.186"}
@@ -0,0 +1,55 @@
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
+ { code: '<h1 className={"bg:black fg:white f:24 m:8 p:8"}>Welcome {name}</h1>' }
16
+ ],
17
+ invalid: [
18
+ {
19
+ code: `
20
+ export interface FakePropsInterface {
21
+ readonly name?: string;
22
+ }
23
+ function Fake({
24
+ name = 'yolo'
25
+ }: FakeProps) {
26
+ return (
27
+ <>
28
+ <h1 className={"m:8 bg:black p:8 fg:white f:24"}>Welcome {name}</h1>
29
+ <p>Bye {name}</p>
30
+ </>
31
+ );
32
+ }
33
+ export default Fake;
34
+ `,
35
+ output: `
36
+ export interface FakePropsInterface {
37
+ readonly name?: string;
38
+ }
39
+ function Fake({
40
+ name = 'yolo'
41
+ }: FakeProps) {
42
+ return (
43
+ <>
44
+ <h1 className={"bg:black fg:white f:24 m:8 p:8"}>Welcome {name}</h1>
45
+ <p>Bye {name}</p>
46
+ </>
47
+ );
48
+ }
49
+ export default Fake;
50
+ `,
51
+ parser: require.resolve('@typescript-eslint/parser'),
52
+ errors: [{ messageId: 'invalidClassOrder' }],
53
+ },
54
+ ]
55
+ })