eslint-plugin-primer-react 6.1.6-rc.f312214 → 6.2.0-rc.2c85052
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/CHANGELOG.md +6 -0
- package/docs/rules/enforce-css-module-identifier-casing.md +39 -0
- package/package-lock.json +27 -60
- package/package.json +5 -5
- package/src/configs/recommended.js +1 -0
- package/src/index.js +1 -0
- package/src/rules/__tests__/enforce-css-module-identifier-casing.test.js +99 -0
- package/src/rules/enforce-css-module-identifier-casing.js +76 -0
- package/src/utils/casing-matches.js +19 -0
- package/src/utils/css-modules.js +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# eslint-plugin-primer-react
|
|
2
2
|
|
|
3
|
+
## 6.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#258](https://github.com/primer/eslint-plugin-primer-react/pull/258) [`83f29f3`](https://github.com/primer/eslint-plugin-primer-react/commit/83f29f339999b9c21d95167bcc2680c1797cbab6) Thanks [@keithamus](https://github.com/keithamus)! - Add enforce-css-module-identifier-casing rule
|
|
8
|
+
|
|
3
9
|
## 6.1.6
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Enforce CSS Module Identifier Casing (enforce-css-module-identifier-casing)
|
|
2
|
+
|
|
3
|
+
CSS Modules should expose class names written in PascalCase.
|
|
4
|
+
|
|
5
|
+
## Rule details
|
|
6
|
+
|
|
7
|
+
This rule disallows the use of any CSS Module property that does not match the desired casing.
|
|
8
|
+
|
|
9
|
+
👎 Examples of **incorrect** code for this rule:
|
|
10
|
+
|
|
11
|
+
```jsx
|
|
12
|
+
/* eslint primer-react/enforce-css-module-identifier-casing: "error" */
|
|
13
|
+
import {Button} from '@primer/react'
|
|
14
|
+
import classes from './some.module.css'
|
|
15
|
+
|
|
16
|
+
<Button className={classes.button} />
|
|
17
|
+
<Button className={classes['button']} />
|
|
18
|
+
<Button className={clsx(classes.button)} />
|
|
19
|
+
|
|
20
|
+
let ButtonClass = "button"
|
|
21
|
+
<Button className={clsx(classes[ButtonClass])} />
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
👍 Examples of **correct** code for this rule:
|
|
25
|
+
|
|
26
|
+
```jsx
|
|
27
|
+
/* eslint primer-react/enforce-css-module-identifier-casing: "error" */
|
|
28
|
+
import {Button} from '@primer/react'
|
|
29
|
+
import classes from './some.module.css'
|
|
30
|
+
;<Button className={classes.Button} />
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Options
|
|
34
|
+
|
|
35
|
+
- `casing` (default: `'pascal'`)
|
|
36
|
+
|
|
37
|
+
By default, the `enforce-css-module-identifier-casing` rule will check for identifiers matching PascalCase.
|
|
38
|
+
Changing this to `'camel'` will instead enforce camelCasing rules. Changing this to `'kebab'` will instead
|
|
39
|
+
enforce kebab-casing rules.
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-primer-react",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.6",
|
|
4
4
|
"lockfileVersion": 2,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "eslint-plugin-primer-react",
|
|
9
|
-
"version": "6.1.
|
|
9
|
+
"version": "6.1.6",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@styled-system/props": "^5.1.5",
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@changesets/changelog-github": "^0.5.0",
|
|
23
|
-
"@changesets/cli": "^2.
|
|
24
|
-
"@github/markdownlint-github": "^0.6.
|
|
23
|
+
"@changesets/cli": "^2.27.9",
|
|
24
|
+
"@github/markdownlint-github": "^0.6.3",
|
|
25
25
|
"@github/prettier-config": "0.0.6",
|
|
26
|
-
"@types/jest": "^29.5.
|
|
26
|
+
"@types/jest": "^29.5.13",
|
|
27
27
|
"@typescript-eslint/rule-tester": "7.16.0",
|
|
28
28
|
"eslint": "^8.42.0",
|
|
29
|
-
"eslint-plugin-prettier": "^5.
|
|
29
|
+
"eslint-plugin-prettier": "^5.2.1",
|
|
30
30
|
"jest": "^29.7.0",
|
|
31
31
|
"markdownlint-cli2": "^0.14.0",
|
|
32
32
|
"markdownlint-cli2-formatter-pretty": "^0.0.7"
|
|
@@ -170,15 +170,6 @@
|
|
|
170
170
|
"url": "https://opencollective.com/babel"
|
|
171
171
|
}
|
|
172
172
|
},
|
|
173
|
-
"node_modules/@babel/core/node_modules/semver": {
|
|
174
|
-
"version": "6.3.1",
|
|
175
|
-
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
|
176
|
-
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
|
177
|
-
"dev": true,
|
|
178
|
-
"bin": {
|
|
179
|
-
"semver": "bin/semver.js"
|
|
180
|
-
}
|
|
181
|
-
},
|
|
182
173
|
"node_modules/@babel/generator": {
|
|
183
174
|
"version": "7.23.0",
|
|
184
175
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
|
|
@@ -219,15 +210,6 @@
|
|
|
219
210
|
"yallist": "^3.0.2"
|
|
220
211
|
}
|
|
221
212
|
},
|
|
222
|
-
"node_modules/@babel/helper-compilation-targets/node_modules/semver": {
|
|
223
|
-
"version": "6.3.1",
|
|
224
|
-
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
|
225
|
-
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
|
226
|
-
"dev": true,
|
|
227
|
-
"bin": {
|
|
228
|
-
"semver": "bin/semver.js"
|
|
229
|
-
}
|
|
230
|
-
},
|
|
231
213
|
"node_modules/@babel/helper-compilation-targets/node_modules/yallist": {
|
|
232
214
|
"version": "3.1.1",
|
|
233
215
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
|
@@ -806,10 +788,11 @@
|
|
|
806
788
|
"dev": true
|
|
807
789
|
},
|
|
808
790
|
"node_modules/@changesets/cli": {
|
|
809
|
-
"version": "2.27.
|
|
810
|
-
"resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.27.
|
|
811
|
-
"integrity": "sha512-
|
|
791
|
+
"version": "2.27.9",
|
|
792
|
+
"resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.27.9.tgz",
|
|
793
|
+
"integrity": "sha512-q42a/ZbDnxPpCb5Wkm6tMVIxgeI9C/bexntzTeCFBrQEdpisQqk8kCHllYZMDjYtEc1ZzumbMJAG8H0Z4rdvjg==",
|
|
812
794
|
"dev": true,
|
|
795
|
+
"license": "MIT",
|
|
813
796
|
"dependencies": {
|
|
814
797
|
"@changesets/apply-release-plan": "^7.0.5",
|
|
815
798
|
"@changesets/assemble-release-plan": "^6.0.4",
|
|
@@ -826,14 +809,12 @@
|
|
|
826
809
|
"@changesets/types": "^6.0.0",
|
|
827
810
|
"@changesets/write": "^0.3.2",
|
|
828
811
|
"@manypkg/get-packages": "^1.1.3",
|
|
829
|
-
"@types/semver": "^7.5.0",
|
|
830
812
|
"ansi-colors": "^4.1.3",
|
|
831
813
|
"ci-info": "^3.7.0",
|
|
832
814
|
"enquirer": "^2.3.0",
|
|
833
815
|
"external-editor": "^3.1.0",
|
|
834
816
|
"fs-extra": "^7.0.1",
|
|
835
817
|
"mri": "^1.2.0",
|
|
836
|
-
"outdent": "^0.5.0",
|
|
837
818
|
"p-limit": "^2.2.0",
|
|
838
819
|
"package-manager-detector": "^0.2.0",
|
|
839
820
|
"picocolors": "^1.1.0",
|
|
@@ -7162,10 +7143,11 @@
|
|
|
7162
7143
|
"dev": true
|
|
7163
7144
|
},
|
|
7164
7145
|
"node_modules/semver": {
|
|
7165
|
-
"version": "6.3.
|
|
7166
|
-
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.
|
|
7167
|
-
"integrity": "sha512-
|
|
7146
|
+
"version": "6.3.1",
|
|
7147
|
+
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
|
7148
|
+
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
|
7168
7149
|
"dev": true,
|
|
7150
|
+
"license": "ISC",
|
|
7169
7151
|
"bin": {
|
|
7170
7152
|
"semver": "bin/semver.js"
|
|
7171
7153
|
}
|
|
@@ -7683,9 +7665,10 @@
|
|
|
7683
7665
|
}
|
|
7684
7666
|
},
|
|
7685
7667
|
"node_modules/tsconfig-paths/node_modules/json5": {
|
|
7686
|
-
"version": "1.0.
|
|
7687
|
-
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.
|
|
7688
|
-
"integrity": "sha512-
|
|
7668
|
+
"version": "1.0.2",
|
|
7669
|
+
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
|
7670
|
+
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
|
7671
|
+
"license": "MIT",
|
|
7689
7672
|
"dependencies": {
|
|
7690
7673
|
"minimist": "^1.2.0"
|
|
7691
7674
|
},
|
|
@@ -8213,14 +8196,6 @@
|
|
|
8213
8196
|
"gensync": "^1.0.0-beta.2",
|
|
8214
8197
|
"json5": "^2.2.3",
|
|
8215
8198
|
"semver": "^6.3.1"
|
|
8216
|
-
},
|
|
8217
|
-
"dependencies": {
|
|
8218
|
-
"semver": {
|
|
8219
|
-
"version": "6.3.1",
|
|
8220
|
-
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
|
8221
|
-
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
|
8222
|
-
"dev": true
|
|
8223
|
-
}
|
|
8224
8199
|
}
|
|
8225
8200
|
},
|
|
8226
8201
|
"@babel/generator": {
|
|
@@ -8257,12 +8232,6 @@
|
|
|
8257
8232
|
"yallist": "^3.0.2"
|
|
8258
8233
|
}
|
|
8259
8234
|
},
|
|
8260
|
-
"semver": {
|
|
8261
|
-
"version": "6.3.1",
|
|
8262
|
-
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
|
8263
|
-
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
|
8264
|
-
"dev": true
|
|
8265
|
-
},
|
|
8266
8235
|
"yallist": {
|
|
8267
8236
|
"version": "3.1.1",
|
|
8268
8237
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
|
@@ -8707,9 +8676,9 @@
|
|
|
8707
8676
|
}
|
|
8708
8677
|
},
|
|
8709
8678
|
"@changesets/cli": {
|
|
8710
|
-
"version": "2.27.
|
|
8711
|
-
"resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.27.
|
|
8712
|
-
"integrity": "sha512-
|
|
8679
|
+
"version": "2.27.9",
|
|
8680
|
+
"resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.27.9.tgz",
|
|
8681
|
+
"integrity": "sha512-q42a/ZbDnxPpCb5Wkm6tMVIxgeI9C/bexntzTeCFBrQEdpisQqk8kCHllYZMDjYtEc1ZzumbMJAG8H0Z4rdvjg==",
|
|
8713
8682
|
"dev": true,
|
|
8714
8683
|
"requires": {
|
|
8715
8684
|
"@changesets/apply-release-plan": "^7.0.5",
|
|
@@ -8727,14 +8696,12 @@
|
|
|
8727
8696
|
"@changesets/types": "^6.0.0",
|
|
8728
8697
|
"@changesets/write": "^0.3.2",
|
|
8729
8698
|
"@manypkg/get-packages": "^1.1.3",
|
|
8730
|
-
"@types/semver": "^7.5.0",
|
|
8731
8699
|
"ansi-colors": "^4.1.3",
|
|
8732
8700
|
"ci-info": "^3.7.0",
|
|
8733
8701
|
"enquirer": "^2.3.0",
|
|
8734
8702
|
"external-editor": "^3.1.0",
|
|
8735
8703
|
"fs-extra": "^7.0.1",
|
|
8736
8704
|
"mri": "^1.2.0",
|
|
8737
|
-
"outdent": "^0.5.0",
|
|
8738
8705
|
"p-limit": "^2.2.0",
|
|
8739
8706
|
"package-manager-detector": "^0.2.0",
|
|
8740
8707
|
"picocolors": "^1.1.0",
|
|
@@ -13303,9 +13270,9 @@
|
|
|
13303
13270
|
"dev": true
|
|
13304
13271
|
},
|
|
13305
13272
|
"semver": {
|
|
13306
|
-
"version": "6.3.
|
|
13307
|
-
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.
|
|
13308
|
-
"integrity": "sha512-
|
|
13273
|
+
"version": "6.3.1",
|
|
13274
|
+
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
|
13275
|
+
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
|
13309
13276
|
"dev": true
|
|
13310
13277
|
},
|
|
13311
13278
|
"set-function-length": {
|
|
@@ -13701,9 +13668,9 @@
|
|
|
13701
13668
|
},
|
|
13702
13669
|
"dependencies": {
|
|
13703
13670
|
"json5": {
|
|
13704
|
-
"version": "1.0.
|
|
13705
|
-
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.
|
|
13706
|
-
"integrity": "sha512-
|
|
13671
|
+
"version": "1.0.2",
|
|
13672
|
+
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
|
13673
|
+
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
|
13707
13674
|
"requires": {
|
|
13708
13675
|
"minimist": "^1.2.0"
|
|
13709
13676
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-primer-react",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0-rc.2c85052",
|
|
4
4
|
"description": "ESLint rules for Primer React",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -39,16 +39,16 @@
|
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@changesets/changelog-github": "^0.5.0",
|
|
42
|
-
"@changesets/cli": "^2.
|
|
43
|
-
"@github/markdownlint-github": "^0.6.
|
|
42
|
+
"@changesets/cli": "^2.27.9",
|
|
43
|
+
"@github/markdownlint-github": "^0.6.3",
|
|
44
44
|
"@github/prettier-config": "0.0.6",
|
|
45
45
|
"eslint": "^8.42.0",
|
|
46
|
-
"eslint-plugin-prettier": "^5.
|
|
46
|
+
"eslint-plugin-prettier": "^5.2.1",
|
|
47
47
|
"jest": "^29.7.0",
|
|
48
48
|
"markdownlint-cli2": "^0.14.0",
|
|
49
49
|
"markdownlint-cli2-formatter-pretty": "^0.0.7",
|
|
50
50
|
"@typescript-eslint/rule-tester": "7.16.0",
|
|
51
|
-
"@types/jest": "^29.5.
|
|
51
|
+
"@types/jest": "^29.5.13"
|
|
52
52
|
},
|
|
53
53
|
"prettier": "@github/prettier-config"
|
|
54
54
|
}
|
|
@@ -20,6 +20,7 @@ module.exports = {
|
|
|
20
20
|
'primer-react/a11y-use-next-tooltip': 'error',
|
|
21
21
|
'primer-react/no-unnecessary-components': 'error',
|
|
22
22
|
'primer-react/prefer-action-list-item-onselect': 'error',
|
|
23
|
+
'primer-react/enforce-css-module-identifier-casing': 'error',
|
|
23
24
|
},
|
|
24
25
|
settings: {
|
|
25
26
|
github: {
|
package/src/index.js
CHANGED
|
@@ -14,6 +14,7 @@ module.exports = {
|
|
|
14
14
|
'no-wildcard-imports': require('./rules/no-wildcard-imports'),
|
|
15
15
|
'no-unnecessary-components': require('./rules/no-unnecessary-components'),
|
|
16
16
|
'prefer-action-list-item-onselect': require('./rules/prefer-action-list-item-onselect'),
|
|
17
|
+
'enforce-css-module-identifier-casing': require('./rules/enforce-css-module-identifier-casing'),
|
|
17
18
|
},
|
|
18
19
|
configs: {
|
|
19
20
|
recommended: require('./configs/recommended'),
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const rule = require('../enforce-css-module-identifier-casing')
|
|
2
|
+
const {RuleTester} = require('eslint')
|
|
3
|
+
|
|
4
|
+
const ruleTester = new RuleTester({
|
|
5
|
+
parserOptions: {
|
|
6
|
+
ecmaVersion: 'latest',
|
|
7
|
+
sourceType: 'module',
|
|
8
|
+
ecmaFeatures: {
|
|
9
|
+
jsx: true,
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
ruleTester.run('enforce-css-module-identifier-casing', rule, {
|
|
15
|
+
valid: [
|
|
16
|
+
'import classes from "a.module.css"; function Foo() { return <Box className={classes.Foo}/> }',
|
|
17
|
+
'import classes from "a.module.css"; function Foo() { return <Box className={clsx(classes.Foo)}/> }',
|
|
18
|
+
'import classes from "a.module.css"; function Foo() { return <Box className={clsx(className, classes.Foo)}/> }',
|
|
19
|
+
'import classes from "a.module.css"; function Foo() { return <Box className={`${classes.Foo}`}/> }',
|
|
20
|
+
'import classes from "a.module.css"; function Foo() { return <Box className={`${classes["Foo"]}`}/> }',
|
|
21
|
+
'import classes from "a.module.css"; let x = "Foo"; function Foo() { return <Box className={`${classes[x]}`}/> }',
|
|
22
|
+
],
|
|
23
|
+
invalid: [
|
|
24
|
+
{
|
|
25
|
+
code: 'import classes from "a.module.css"; function Foo() { return <Box className={classes.foo}/> }',
|
|
26
|
+
errors: [
|
|
27
|
+
{
|
|
28
|
+
messageId: 'pascal',
|
|
29
|
+
data: {name: 'foo'},
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
code: 'import classes from "a.module.css"; function Foo() { return <Box className={clsx(classes.foo)}/> }',
|
|
35
|
+
errors: [
|
|
36
|
+
{
|
|
37
|
+
messageId: 'pascal',
|
|
38
|
+
data: {name: 'foo'},
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
code: 'import classes from "a.module.css"; function Foo() { return <Box className={clsx(className, classes.foo)}/> }',
|
|
44
|
+
errors: [
|
|
45
|
+
{
|
|
46
|
+
messageId: 'pascal',
|
|
47
|
+
data: {name: 'foo'},
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
code: 'import classes from "a.module.css"; function Foo() { return <Box className={`${classes.foo}`}/> }',
|
|
53
|
+
errors: [
|
|
54
|
+
{
|
|
55
|
+
messageId: 'pascal',
|
|
56
|
+
data: {name: 'foo'},
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
code: 'import classes from "a.module.css"; function Foo() { return <Box className={classes["foo"]}/> }',
|
|
62
|
+
errors: [
|
|
63
|
+
{
|
|
64
|
+
messageId: 'pascal',
|
|
65
|
+
data: {name: 'foo'},
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
code: 'import classes from "a.module.css"; function Foo() { return <Box className={classes.Foo}/> }',
|
|
71
|
+
options: [{casing: 'camel'}],
|
|
72
|
+
errors: [
|
|
73
|
+
{
|
|
74
|
+
messageId: 'camel',
|
|
75
|
+
data: {name: 'Foo'},
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
code: 'import classes from "a.module.css"; let FooClass = "foo"; function Foo() { return <Box className={classes[FooClass]}/> }',
|
|
81
|
+
errors: [
|
|
82
|
+
{
|
|
83
|
+
messageId: 'pascal',
|
|
84
|
+
data: {name: 'foo'},
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
code: 'import classes from "a.module.css"; function Foo() { return <Box className={classes[x]}/> }',
|
|
90
|
+
options: [{casing: 'camel'}],
|
|
91
|
+
errors: [
|
|
92
|
+
{
|
|
93
|
+
messageId: 'bad',
|
|
94
|
+
data: {type: 'Identifier'},
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
})
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const {availableCasings, casingMatches} = require('../utils/casing-matches')
|
|
2
|
+
const {identifierIsCSSModuleBinding} = require('../utils/css-modules')
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
meta: {
|
|
6
|
+
type: 'suggestion',
|
|
7
|
+
fixable: 'code',
|
|
8
|
+
schema: [
|
|
9
|
+
{
|
|
10
|
+
properties: {
|
|
11
|
+
casing: {
|
|
12
|
+
enum: availableCasings,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
messages: {
|
|
18
|
+
bad: 'Class names should be in a recognisable case, and either an identifier or literal, saw: {{ type }}',
|
|
19
|
+
camel: 'Class names should be camelCase in both CSS and JS, saw: {{ name }}',
|
|
20
|
+
pascal: 'Class names should be PascalCase in both CSS and JS, saw: {{ name }}',
|
|
21
|
+
kebab: 'Class names should be kebab-case in both CSS and JS, saw: {{ name }}',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
create(context) {
|
|
25
|
+
const casing = context.options[0]?.casing || 'pascal'
|
|
26
|
+
return {
|
|
27
|
+
['JSXAttribute[name.name="className"] JSXExpressionContainer MemberExpression[object.type="Identifier"]']:
|
|
28
|
+
function (node) {
|
|
29
|
+
if (!identifierIsCSSModuleBinding(node.object, context)) return
|
|
30
|
+
if (!node.computed && node.property?.type === 'Identifier') {
|
|
31
|
+
if (!casingMatches(node.property.name || '', casing)) {
|
|
32
|
+
context.report({
|
|
33
|
+
node: node.property,
|
|
34
|
+
messageId: casing,
|
|
35
|
+
data: {name: node.property.name},
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
} else if (node.property?.type === 'Literal') {
|
|
39
|
+
if (!casingMatches(node.property.value || '', casing)) {
|
|
40
|
+
context.report({
|
|
41
|
+
node: node.property,
|
|
42
|
+
messageId: casing,
|
|
43
|
+
data: {name: node.property.value},
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
} else if (node.computed) {
|
|
47
|
+
const ref = context
|
|
48
|
+
.getScope()
|
|
49
|
+
.references.find(reference => reference.identifier.name === node.property.name)
|
|
50
|
+
const def = ref.resolved?.defs?.[0]
|
|
51
|
+
if (def?.node?.init?.type === 'Literal') {
|
|
52
|
+
if (!casingMatches(def.node.init.value || '', casing)) {
|
|
53
|
+
context.report({
|
|
54
|
+
node: node.property,
|
|
55
|
+
messageId: casing,
|
|
56
|
+
data: {name: def.node.init.value},
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
context.report({
|
|
61
|
+
node: node.property,
|
|
62
|
+
messageId: 'bad',
|
|
63
|
+
data: {type: node.property.type},
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
context.report({
|
|
68
|
+
node: node.property,
|
|
69
|
+
messageId: 'bad',
|
|
70
|
+
data: {type: node.property.type},
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const camelReg = /^[a-z]+(?:[A-Z0-9][a-z0-9]+)*?$/
|
|
2
|
+
const pascalReg = /^(?:[A-Z0-9][a-z0-9]+)+?$/
|
|
3
|
+
const kebabReg = /^[a-z]+(?:-[a-z0-9]+)*?$/
|
|
4
|
+
|
|
5
|
+
function casingMatches(name, type) {
|
|
6
|
+
switch (type) {
|
|
7
|
+
case 'camel':
|
|
8
|
+
return camelReg.test(name)
|
|
9
|
+
case 'pascal':
|
|
10
|
+
return pascalReg.test(name)
|
|
11
|
+
case 'kebab':
|
|
12
|
+
return kebabReg.test(name)
|
|
13
|
+
default:
|
|
14
|
+
throw new Error(`Invalid case type ${type}`)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.casingMatches = casingMatches
|
|
18
|
+
|
|
19
|
+
exports.availableCasings = ['camel', 'pascal', 'kebab']
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function importBindingIsFromCSSModuleImport(node) {
|
|
2
|
+
return node.type === 'ImportBinding' && node.parent?.source?.value?.endsWith('.module.css')
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function identifierIsCSSModuleBinding(node, context) {
|
|
6
|
+
if (node.type !== 'Identifier') return false
|
|
7
|
+
const ref = context.getScope().references.find(reference => reference.identifier.name === node.name)
|
|
8
|
+
if (ref.resolved?.defs?.some(importBindingIsFromCSSModuleImport)) {
|
|
9
|
+
return true
|
|
10
|
+
}
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
exports.importBindingIsFromCSSModuleImport = importBindingIsFromCSSModuleImport
|
|
15
|
+
exports.identifierIsCSSModuleBinding = identifierIsCSSModuleBinding
|