@culur/config-stylelint 0.0.0-dev-20240527064403

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +122 -0
  3. package/dist/index.cjs +190 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +5 -0
  6. package/dist/index.d.ts +5 -0
  7. package/dist/index.js +188 -0
  8. package/dist/index.js.map +1 -0
  9. package/package.json +100 -0
  10. package/src/__tests__/dedent.ts +5 -0
  11. package/src/__tests__/index.ts +5 -0
  12. package/src/__tests__/mock.ts +36 -0
  13. package/src/__tests__/syntax.ts +11 -0
  14. package/src/__tests__/test-lint-and-fix.ts +55 -0
  15. package/src/__tests__/test-rule-or-config-value.ts +68 -0
  16. package/src/configs/css.test.ts +10 -0
  17. package/src/configs/css.ts +16 -0
  18. package/src/configs/scss.test.ts +9 -0
  19. package/src/configs/scss.ts +11 -0
  20. package/src/configs/shared.test.ts +9 -0
  21. package/src/configs/shared.ts +5 -0
  22. package/src/configs/vue.test.ts +18 -0
  23. package/src/configs/vue.ts +10 -0
  24. package/src/factory.test.ts +15 -0
  25. package/src/factory.ts +23 -0
  26. package/src/index.test.ts +9 -0
  27. package/src/index.ts +3 -0
  28. package/src/rules/at-rule-no-unknown.test.ts +43 -0
  29. package/src/rules/at-rule-no-unknown.ts +33 -0
  30. package/src/rules/declaration-block-no-redundant-longhand-properties.test.ts +38 -0
  31. package/src/rules/declaration-block-no-redundant-longhand-properties.ts +11 -0
  32. package/src/rules/dollar-variable-pattern.test.ts +27 -0
  33. package/src/rules/dollar-variable-pattern.ts +18 -0
  34. package/src/rules/function-no-unknown.test.ts +39 -0
  35. package/src/rules/function-no-unknown.ts +35 -0
  36. package/src/rules/order.test.ts +46 -0
  37. package/src/rules/order.ts +108 -0
  38. package/src/utils/index.ts +1 -0
  39. package/src/utils/utils.test.ts +46 -0
  40. package/src/utils/utils.ts +44 -0
package/package.json ADDED
@@ -0,0 +1,100 @@
1
+ {
2
+ "name": "@culur/config-stylelint",
3
+ "version": "0.0.0-dev-20240527064403",
4
+ "description": "Culur's stylelint configuration",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/culur/culur.git",
11
+ "directory": "packages/config-stylelint"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/culur/culur/issues"
15
+ },
16
+ "homepage": "https://github.com/culur/culur#readme",
17
+ "author": "culur <culur.net@gmail.com>",
18
+ "license": "MIT",
19
+ "keywords": [
20
+ "culur",
21
+ "config",
22
+ "stylelint"
23
+ ],
24
+ "type": "module",
25
+ "exports": {
26
+ ".": {
27
+ "import": {
28
+ "types": "./dist/index.d.ts",
29
+ "default": "./dist/index.js"
30
+ },
31
+ "require": {
32
+ "types": "./dist/index.d.cts",
33
+ "default": "./dist/index.cjs"
34
+ }
35
+ },
36
+ "./factory": {
37
+ "import": {
38
+ "types": "./dist/factory.d.ts",
39
+ "default": "./dist/factory.js"
40
+ },
41
+ "require": {
42
+ "types": "./dist/factory.d.cts",
43
+ "default": "./dist/factory.cjs"
44
+ }
45
+ }
46
+ },
47
+ "files": [
48
+ "!**/*.json",
49
+ "!**/*.test.*",
50
+ ".stylelintrc.js",
51
+ "CHANGELOG.md",
52
+ "LICENSE",
53
+ "README.md",
54
+ "dist",
55
+ "src"
56
+ ],
57
+ "scripts": {
58
+ "clean": "rimraf dist coverage",
59
+ "build": "tsup",
60
+ "test": "tsc --noEmit && vitest run"
61
+ },
62
+ "volta": {
63
+ "extends": "../../package.json"
64
+ },
65
+ "engines": {
66
+ "node": ">=20"
67
+ },
68
+ "dependencies": {
69
+ "@culur/utils-packages": "workspace:*",
70
+ "stylelint-config-clean-order": "^5.4.2",
71
+ "stylelint-config-html": "^1.1.0",
72
+ "stylelint-config-standard": "^36.0.0",
73
+ "stylelint-config-standard-scss": "^13.1.0",
74
+ "stylelint-config-standard-vue": "^1.0.0",
75
+ "stylelint-order": "^6.0.4",
76
+ "stylelint-scss": "^6.3.0",
77
+ "stylelint-selector-bem-pattern": "^4.0.0"
78
+ },
79
+ "devDependencies": {
80
+ "@culur/config-tsup": "workspace:*",
81
+ "@culur/config-typescript": "workspace:*",
82
+ "@culur/config-vite": "workspace:*",
83
+ "@types/node": "^20.12.12",
84
+ "@vitest/coverage-v8": "^1.6.0",
85
+ "@vitest/ui": "^1.6.0",
86
+ "dedent": "^1.5.3",
87
+ "postcss": "^8.4.38",
88
+ "postcss-html": "^1.7.0",
89
+ "postcss-sass": "^0.5.0",
90
+ "postcss-scss": "^4.0.9",
91
+ "rimraf": "^5.0.7",
92
+ "stylelint": "^16.6.0",
93
+ "tsup": "^8.0.2",
94
+ "typescript": "^5.4.5",
95
+ "vitest": "^1.6.0"
96
+ },
97
+ "peerDependencies": {
98
+ "stylelint": ">=16.0.0"
99
+ }
100
+ }
@@ -0,0 +1,5 @@
1
+ import dedent from 'dedent';
2
+
3
+ export const css = dedent;
4
+ export const scss = dedent;
5
+ export const html = dedent;
@@ -0,0 +1,5 @@
1
+ export * from './dedent';
2
+ export * from './mock';
3
+ export * from './syntax';
4
+ export * from './test-rule-or-config-value';
5
+ export * from './test-lint-and-fix';
@@ -0,0 +1,36 @@
1
+ import { vi } from 'vitest';
2
+
3
+ export type UtilsPackages = Partial<{
4
+ hasTailwind: boolean;
5
+ hasSass: boolean;
6
+ hasVue: boolean;
7
+ }>;
8
+
9
+ export const defineMockUtilsPackages = (testCases: UtilsPackages[]) => {
10
+ const shouldMockUtilsPackages = testCases.some(
11
+ t =>
12
+ typeof t.hasTailwind === 'boolean' ||
13
+ typeof t.hasSass === 'boolean' ||
14
+ typeof t.hasVue === 'boolean',
15
+ );
16
+
17
+ if (shouldMockUtilsPackages) {
18
+ vi.mock('@culur/utils-packages');
19
+ }
20
+
21
+ return async ({ hasTailwind, hasSass, hasVue }: UtilsPackages) => {
22
+ if (shouldMockUtilsPackages) {
23
+ const utilsPackages = await import('@culur/utils-packages');
24
+
25
+ if (typeof hasTailwind === 'boolean') {
26
+ utilsPackages.hasTailwind = vi.fn().mockReturnValue(hasTailwind);
27
+ }
28
+ if (typeof hasSass === 'boolean') {
29
+ utilsPackages.hasSass = vi.fn().mockReturnValue(hasSass);
30
+ }
31
+ if (typeof hasVue === 'boolean') {
32
+ utilsPackages.hasVue = vi.fn().mockReturnValue(hasVue);
33
+ }
34
+ }
35
+ };
36
+ };
@@ -0,0 +1,11 @@
1
+ import type { Config } from 'stylelint';
2
+ import postcssScss from 'postcss-scss';
3
+
4
+ type OverrideSyntax = Config['overrides'] extends (infer T)[] | undefined
5
+ ? T
6
+ : never;
7
+
8
+ export const syntaxScss: OverrideSyntax = {
9
+ files: ['*.scss', '**/*.scss'],
10
+ customSyntax: postcssScss,
11
+ };
@@ -0,0 +1,55 @@
1
+ import { resolve } from 'node:path';
2
+ import type { Config } from 'stylelint';
3
+ import stylelint from 'stylelint';
4
+ import type { Awaitable } from 'vitest';
5
+ import { describe, expect, it } from 'vitest';
6
+ import type { UtilsPackages } from './mock';
7
+ import { defineMockUtilsPackages } from './mock';
8
+
9
+ type LintAndFixCase = {
10
+ code: string;
11
+ fixedCode?: string;
12
+ isError?: boolean;
13
+ ext?: 'css' | 'scss' | 'vue';
14
+ debug?: boolean;
15
+ } & UtilsPackages;
16
+
17
+ export const testLintAndFix = (
18
+ getConfig: Config | (() => Awaitable<Config>),
19
+ testCases: LintAndFixCase[],
20
+ ) =>
21
+ describe('test lint and fix', () => {
22
+ const mockUtilsPackages = defineMockUtilsPackages(testCases);
23
+
24
+ const name = [
25
+ ...(['hasTailwind', 'hasSass', 'hasVue', 'isError'] as const) //
26
+ .filter(field => testCases.some(t => typeof t[field] === 'boolean')),
27
+ ...(['code', 'fixedCode'] as const) //
28
+ .filter(field => testCases.some(t => typeof t[field] === 'string')),
29
+ ]
30
+ .map(field => `${field} = $${field}`)
31
+ .join(', ');
32
+
33
+ it.each(testCases)(name, async options => {
34
+ await mockUtilsPackages(options);
35
+
36
+ const config =
37
+ typeof getConfig === 'function' ? await getConfig() : getConfig;
38
+ const result = await stylelint.lint({
39
+ code: options.code,
40
+ codeFilename: `filename.${options.ext ?? 'css'}`,
41
+ config,
42
+ fix: 'fixedCode' in options,
43
+ configBasedir: resolve(__dirname, '..'),
44
+ });
45
+
46
+ if (options.debug) {
47
+ /* eslint-disable no-console */
48
+ console.log(result.results.flatMap(r => r.warnings));
49
+ console.log(result.code);
50
+ /* eslint-enable no-console */
51
+ }
52
+ if ('fixedCode' in options) expect(result.code).toBe(options.fixedCode);
53
+ if ('isError' in options) expect(result.errored).toBe(options.isError);
54
+ });
55
+ });
@@ -0,0 +1,68 @@
1
+ import type { Awaitable } from 'vitest';
2
+ import { describe, it } from 'vitest';
3
+ import type { Config, ConfigRuleSettings } from 'stylelint';
4
+ import type { UtilsPackages } from './mock';
5
+ import { defineMockUtilsPackages } from './mock';
6
+
7
+ import type {
8
+ ConfigRulePrimaryOption,
9
+ ConfigRuleSecondaryOptions,
10
+ } from '~/utils';
11
+
12
+ type TArgs<TOptions extends object, TRuleOrConfig> = [
13
+ getRuleOrConfig: (() => Awaitable<TRuleOrConfig>) | TRuleOrConfig,
14
+ fn: (
15
+ ruleOrConfig: TRuleOrConfig,
16
+ options: UtilsPackages & TOptions,
17
+ ) => Promise<void> | void,
18
+ ];
19
+
20
+ const testRuleOrConfigValue = <
21
+ TBaseRuleOrConfig extends
22
+ | ConfigRuleSettings<ConfigRulePrimaryOption, ConfigRuleSecondaryOptions>
23
+ | Config,
24
+ >(
25
+ ruleOrConfig: 'rule' | 'config',
26
+ ) => {
27
+ return <
28
+ TCase extends UtilsPackages & object,
29
+ TRuleOrConfig extends TBaseRuleOrConfig,
30
+ >(
31
+ ...args:
32
+ | [testCases: TCase[], ...TArgs<TCase, TRuleOrConfig>]
33
+ | [...TArgs<TCase, TRuleOrConfig>]
34
+ ) => {
35
+ describe(ruleOrConfig, () => {
36
+ const [testCases, getRuleOrConfig, fn] =
37
+ args.length === 3
38
+ ? [...args]
39
+ : [[{} as UtilsPackages & TCase], ...args];
40
+
41
+ const mockUtilsPackages = defineMockUtilsPackages(testCases);
42
+
43
+ const name = (['hasTailwind', 'hasSass', 'hasVue'] as const)
44
+ .filter(field => testCases.some(t => typeof t[field] === 'boolean'))
45
+ .map(field => `${field} = $${field}`)
46
+ .join(', ');
47
+
48
+ it.each(testCases)(name, async options => {
49
+ await mockUtilsPackages(options);
50
+
51
+ const ruleOrConfig =
52
+ typeof getRuleOrConfig === 'function'
53
+ ? await getRuleOrConfig()
54
+ : getRuleOrConfig;
55
+
56
+ await fn(ruleOrConfig, options);
57
+ });
58
+ });
59
+ };
60
+ };
61
+
62
+ export const testConfigValue = testRuleOrConfigValue<Config>('config');
63
+ export const testRuleValue = testRuleOrConfigValue<
64
+ ConfigRuleSettings<
65
+ ConfigRulePrimaryOption, //
66
+ ConfigRuleSecondaryOptions
67
+ >
68
+ >('rule');
@@ -0,0 +1,10 @@
1
+ import { assert, describe } from 'vitest';
2
+ import { configCss } from './css';
3
+ import { testConfigValue } from '~/__tests__';
4
+
5
+ describe('css config', () => {
6
+ testConfigValue(configCss, config => {
7
+ assert(Array.isArray(config.extends));
8
+ assert(Array.isArray(config.plugins));
9
+ });
10
+ });
@@ -0,0 +1,16 @@
1
+ import type { Config } from 'stylelint';
2
+ import { atRuleNoUnknownCSS } from '~/rules/at-rule-no-unknown';
3
+ import { declarationBlockNoRedundantLonghandProperties } from '~/rules/declaration-block-no-redundant-longhand-properties';
4
+ import { functionNoUnknown } from '~/rules/function-no-unknown';
5
+ import { order } from '~/rules/order';
6
+
7
+ export const configCss = (): Config => ({
8
+ extends: ['stylelint-config-standard', 'stylelint-config-clean-order'],
9
+ plugins: ['stylelint-order', 'stylelint-selector-bem-pattern'],
10
+ rules: {
11
+ ...atRuleNoUnknownCSS(),
12
+ ...declarationBlockNoRedundantLonghandProperties,
13
+ ...functionNoUnknown(),
14
+ ...order(),
15
+ },
16
+ });
@@ -0,0 +1,9 @@
1
+ import { assert, describe } from 'vitest';
2
+ import { configScss } from './scss';
3
+ import { testConfigValue } from '~/__tests__';
4
+
5
+ describe('scss config', () => {
6
+ testConfigValue(configScss, config => {
7
+ assert(Array.isArray(config.extends));
8
+ });
9
+ });
@@ -0,0 +1,11 @@
1
+ import type { Config } from 'stylelint';
2
+ import { atRuleNoUnknownSCSS } from '~/rules/at-rule-no-unknown';
3
+ import { dollarVariablePatternSCSS } from '~/rules/dollar-variable-pattern';
4
+
5
+ export const configScss = (): Config => ({
6
+ extends: ['stylelint-config-standard-scss'],
7
+ rules: {
8
+ ...atRuleNoUnknownSCSS(),
9
+ ...dollarVariablePatternSCSS,
10
+ },
11
+ });
@@ -0,0 +1,9 @@
1
+ import { assert, describe } from 'vitest';
2
+ import { configShared } from './shared';
3
+ import { testConfigValue } from '~/__tests__';
4
+
5
+ describe('shared config', () => {
6
+ testConfigValue(configShared, config => {
7
+ assert(Array.isArray(config.extends));
8
+ });
9
+ });
@@ -0,0 +1,5 @@
1
+ import type { Config } from 'stylelint';
2
+
3
+ export const configShared = (): Config => ({
4
+ extends: ['stylelint-config-html'],
5
+ });
@@ -0,0 +1,18 @@
1
+ import { assert, describe } from 'vitest';
2
+ import { configVue } from './vue';
3
+ import { testConfigValue } from '~/__tests__';
4
+
5
+ describe('vue config', () => {
6
+ testConfigValue(
7
+ [{ hasSass: true }, { hasSass: false }],
8
+ configVue,
9
+ (config, options) => {
10
+ assert(Array.isArray(config.extends));
11
+ config.extends.includes(
12
+ options.hasSass
13
+ ? 'stylelint-config-standard-vue/scss'
14
+ : 'stylelint-config-standard-vue',
15
+ );
16
+ },
17
+ );
18
+ });
@@ -0,0 +1,10 @@
1
+ import { hasSass } from '@culur/utils-packages';
2
+ import type { Config } from 'stylelint';
3
+
4
+ export const configVue = (): Config => ({
5
+ extends: [
6
+ hasSass()
7
+ ? 'stylelint-config-standard-vue/scss'
8
+ : 'stylelint-config-standard-vue',
9
+ ],
10
+ });
@@ -0,0 +1,15 @@
1
+ import { css, testLintAndFix } from './__tests__';
2
+ import defineConfig from './factory';
3
+
4
+ testLintAndFix(defineConfig, [
5
+ {
6
+ code: css`
7
+ a {
8
+ color: red;
9
+ }
10
+ `,
11
+ hasSass: true,
12
+ hasVue: true,
13
+ isError: false,
14
+ },
15
+ ]);
package/src/factory.ts ADDED
@@ -0,0 +1,23 @@
1
+ import type { Packages } from '@culur/utils-packages';
2
+ import { hasSass, hasVue, updateDefaultPackages } from '@culur/utils-packages';
3
+ import type { Config } from 'stylelint';
4
+ import { configCss } from './configs/css';
5
+ import { configScss } from './configs/scss';
6
+ import { configShared } from './configs/shared';
7
+ import { configVue } from './configs/vue';
8
+ import { mergeConfigs } from './utils';
9
+
10
+ export default function defineConfig(
11
+ options?: Partial<Packages>,
12
+ config?: Config,
13
+ ): Config {
14
+ updateDefaultPackages(options);
15
+
16
+ return mergeConfigs(
17
+ configShared(),
18
+ configCss(),
19
+ hasSass() && configScss(),
20
+ hasVue() && configVue(),
21
+ config,
22
+ );
23
+ }
@@ -0,0 +1,9 @@
1
+ import { describe, expectTypeOf, it } from 'vitest';
2
+ import type { Config } from 'stylelint';
3
+ import defineConfig from '.';
4
+
5
+ describe('index', () => {
6
+ it('root config', () => {
7
+ expectTypeOf(defineConfig).toEqualTypeOf<Config>();
8
+ });
9
+ });
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import defineConfig from './factory';
2
+
3
+ export default defineConfig();
@@ -0,0 +1,43 @@
1
+ import { assert, expect } from 'vitest';
2
+ import { syntaxScss, testLintAndFix } from '~/__tests__';
3
+ import { testRuleValue } from '~/__tests__/test-rule-or-config-value';
4
+
5
+ testRuleValue(
6
+ [
7
+ { hasTailwind: false, length: 0 },
8
+ { hasTailwind: true, length: 7 },
9
+ ],
10
+ async () => {
11
+ const module = await import('./at-rule-no-unknown');
12
+ return module.atRuleNoUnknownRule();
13
+ },
14
+ (rule, options) => {
15
+ assert(Array.isArray(rule));
16
+ assert(typeof rule[1] === 'object');
17
+ expect(rule[1].ignoreAtRules).toHaveLength(options.length);
18
+ },
19
+ );
20
+
21
+ testLintAndFix(async () => {
22
+ const module = await import('./at-rule-no-unknown');
23
+ return {
24
+ rules: module.atRuleNoUnknownCSS(),
25
+ };
26
+ }, [
27
+ { hasTailwind: false, code: '@unknown {}', isError: true },
28
+ { hasTailwind: false, code: '@media {}', isError: false },
29
+ { hasTailwind: true, code: '@tailwind {}', isError: false },
30
+ ]);
31
+
32
+ testLintAndFix(async () => {
33
+ const module = await import('./at-rule-no-unknown');
34
+ return {
35
+ plugins: ['stylelint-scss'],
36
+ rules: module.atRuleNoUnknownSCSS(),
37
+ overrides: [syntaxScss],
38
+ };
39
+ }, [
40
+ { ext: 'scss', hasTailwind: false, code: '@unknown {}', isError: true },
41
+ { ext: 'scss', hasTailwind: false, code: '@media {}', isError: false },
42
+ { ext: 'scss', hasTailwind: true, code: '@tailwind {}', isError: false },
43
+ ]);
@@ -0,0 +1,33 @@
1
+ import { hasTailwind } from '@culur/utils-packages';
2
+ import { defineRule, defineRules } from '~/utils';
3
+
4
+ // https://tailwindcss.com/docs/functions-and-directives
5
+ const directives = [
6
+ 'tailwind',
7
+ 'layer',
8
+ 'apply',
9
+ 'config',
10
+
11
+ // https://v2.tailwindcss.com/docs/functions-and-directives
12
+ // backwards compatible for v2
13
+ 'variants',
14
+ 'responsive',
15
+ 'screen',
16
+ ];
17
+
18
+ export const atRuleNoUnknownRule = () =>
19
+ defineRule<boolean, { ignoreAtRules: (string | RegExp)[] }>([
20
+ true,
21
+ { ignoreAtRules: hasTailwind() ? directives : [] },
22
+ ]);
23
+
24
+ export const atRuleNoUnknownCSS = () =>
25
+ defineRules({
26
+ 'at-rule-no-unknown': atRuleNoUnknownRule(),
27
+ });
28
+
29
+ export const atRuleNoUnknownSCSS = () =>
30
+ defineRules({
31
+ 'at-rule-no-unknown': null,
32
+ 'scss/at-rule-no-unknown': atRuleNoUnknownRule(),
33
+ });
@@ -0,0 +1,38 @@
1
+ import { assert, expect } from 'vitest';
2
+ import {
3
+ declarationBlockNoRedundantLonghandProperties,
4
+ declarationBlockNoRedundantLonghandPropertiesRule,
5
+ } from './declaration-block-no-redundant-longhand-properties';
6
+ import { css, testLintAndFix, testRuleValue } from '~/__tests__';
7
+
8
+ testRuleValue(
9
+ declarationBlockNoRedundantLonghandPropertiesRule, //
10
+ rule => {
11
+ assert(Array.isArray(rule));
12
+ assert(typeof rule[1] === 'object');
13
+ expect(rule[1].ignoreShorthands).toHaveLength(1);
14
+ },
15
+ );
16
+
17
+ testLintAndFix(
18
+ {
19
+ rules: declarationBlockNoRedundantLonghandProperties,
20
+ },
21
+ [
22
+ {
23
+ code: css`
24
+ a {
25
+ margin-top: 1px;
26
+ margin-right: 2px;
27
+ margin-bottom: 3px;
28
+ margin-left: 4px;
29
+ }
30
+ `,
31
+ fixedCode: css`
32
+ a {
33
+ margin: 1px 2px 3px 4px;
34
+ }
35
+ `,
36
+ },
37
+ ],
38
+ );
@@ -0,0 +1,11 @@
1
+ import { defineRule, defineRules } from '~/utils';
2
+
3
+ export const declarationBlockNoRedundantLonghandPropertiesRule = defineRule<
4
+ true,
5
+ { ignoreShorthands?: (string | RegExp)[] }
6
+ >([true, { ignoreShorthands: ['grid-template'] }]);
7
+
8
+ export const declarationBlockNoRedundantLonghandProperties = defineRules({
9
+ 'declaration-block-no-redundant-longhand-properties':
10
+ declarationBlockNoRedundantLonghandPropertiesRule,
11
+ });
@@ -0,0 +1,27 @@
1
+ import { assert, expectTypeOf } from 'vitest';
2
+ import {
3
+ dollarVariablePatternRule,
4
+ dollarVariablePatternSCSS,
5
+ } from './dollar-variable-pattern';
6
+ import { scss, testLintAndFix, testRuleValue } from '~/__tests__';
7
+
8
+ testRuleValue(
9
+ () => dollarVariablePatternRule,
10
+ rule => {
11
+ assert(Array.isArray(rule));
12
+ assert(typeof rule[1] === 'object');
13
+ expectTypeOf(rule[1].message).toBeString();
14
+ },
15
+ );
16
+
17
+ testLintAndFix(
18
+ {
19
+ extends: ['stylelint-config-standard-scss'],
20
+ rules: dollarVariablePatternSCSS,
21
+ }, //
22
+ [
23
+ { isError: false, code: scss`a { $kebab-case: 123; }` },
24
+ { isError: true, code: scss`a { $snake_case: 123; }` },
25
+ { isError: true, code: scss`a { $camelCase: 123; }` },
26
+ ],
27
+ );
@@ -0,0 +1,18 @@
1
+ import { defineRule, defineRules } from '~/utils';
2
+
3
+ type PrimaryOption = string;
4
+ interface SecondaryOptions {
5
+ message: string;
6
+ }
7
+
8
+ export const dollarVariablePatternRule = defineRule<
9
+ PrimaryOption,
10
+ SecondaryOptions
11
+ >([
12
+ '^(-{0,2}?[a-z][a-z0-9]*)(-[a-z0-9]+)*$',
13
+ { message: 'Expected variable to be kebab-case' },
14
+ ]);
15
+
16
+ export const dollarVariablePatternSCSS = defineRules({
17
+ 'scss/dollar-variable-pattern': dollarVariablePatternRule,
18
+ });
@@ -0,0 +1,39 @@
1
+ import { assert, expect } from 'vitest';
2
+ import { testLintAndFix, testRuleValue } from '~/__tests__';
3
+
4
+ testRuleValue(
5
+ [
6
+ { hasTailwind: true, hasSass: false, hasVue: false, length: 2 },
7
+ { hasTailwind: false, hasSass: true, hasVue: false, length: 1 },
8
+ { hasTailwind: false, hasSass: false, hasVue: true, length: 1 },
9
+ ],
10
+ async () => {
11
+ const module = await import('./function-no-unknown');
12
+ return module.functionNoUnknownRule();
13
+ },
14
+ (rule, options) => {
15
+ assert(Array.isArray(rule));
16
+ assert(typeof rule[1] === 'object');
17
+ expect(rule[1].ignoreFunctions).toHaveLength(options.length);
18
+ },
19
+ );
20
+
21
+ testLintAndFix(async () => {
22
+ const module = await import('./function-no-unknown');
23
+ return { rules: module.functionNoUnknown() };
24
+ }, [
25
+ {
26
+ isError: true,
27
+ code: 'a { color: unknown(1); }',
28
+ },
29
+ {
30
+ hasTailwind: true,
31
+ isError: false,
32
+ code: 'a { color: theme(colors.blue.500); }',
33
+ },
34
+ {
35
+ hasVue: true,
36
+ isError: false,
37
+ code: 'a { color: v-bind(colors); }',
38
+ },
39
+ ]);