@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
@@ -0,0 +1,35 @@
1
+ import { hasSass, hasTailwind, hasVue } from '@culur/utils-packages';
2
+ import { defineRule, defineRules } from '~/utils';
3
+
4
+ export const functionNoUnknownRule = () => {
5
+ const ignoreFunctions = [
6
+ // https://tailwindcss.com/docs/functions-and-directives#functions
7
+ ...(hasTailwind() //
8
+ ? ['theme', 'screen']
9
+ : []),
10
+
11
+ // https://sass-lang.com/documentation/modules/list
12
+ // https://sass-lang.com/documentation/modules/math
13
+ // https://sass-lang.com/documentation/modules/color
14
+ ...(hasSass() //
15
+ ? [/^(list|math|color)\..+/]
16
+ : []),
17
+
18
+ // https://vuejs.org/api/sfc-css-features.html#v-bind-in-css
19
+ ...(hasVue() //
20
+ ? ['v-bind']
21
+ : []),
22
+ ];
23
+
24
+ return defineRule<boolean, { ignoreFunctions?: (string | RegExp)[] }>([
25
+ true,
26
+ ignoreFunctions.length > 0
27
+ ? { ignoreFunctions }
28
+ : { ignoreFunctions: undefined },
29
+ ]);
30
+ };
31
+
32
+ export const functionNoUnknown = () =>
33
+ defineRules({
34
+ 'function-no-unknown': functionNoUnknownRule(),
35
+ });
@@ -0,0 +1,46 @@
1
+ import { assert, expect } from 'vitest';
2
+ import { scss, testLintAndFix, testRuleValue } from '~/__tests__';
3
+
4
+ testRuleValue(
5
+ [
6
+ { hasTailwind: false, hasSass: false, length: 8 },
7
+ { hasTailwind: true, hasSass: false, length: 15 },
8
+ { hasTailwind: false, hasSass: true, length: 16 },
9
+ { hasTailwind: true, hasSass: true, length: 23 },
10
+ ],
11
+ async () => {
12
+ const module = await import('./order');
13
+ return module.orderRule();
14
+ },
15
+ (rule, options) => {
16
+ assert(Array.isArray(rule));
17
+
18
+ assert(Array.isArray(rule[0]));
19
+ expect(rule[0]).toHaveLength(options.length);
20
+
21
+ assert(typeof rule[1] === 'object');
22
+ expect(rule[1]).toStrictEqual({ unspecified: 'bottom' });
23
+ },
24
+ );
25
+
26
+ testLintAndFix(async () => {
27
+ const module = await import('./order');
28
+ return {
29
+ plugins: ['stylelint-order'],
30
+ rules: module.order(),
31
+ };
32
+ }, [
33
+ {
34
+ hasTailwind: false,
35
+ hasSass: false,
36
+ code: scss`
37
+ a {
38
+ $variable: 1px;
39
+ --property: 3px;
40
+ @variable: 2px;
41
+ width: 4px;
42
+ }
43
+ `,
44
+ isError: false,
45
+ },
46
+ ]);
@@ -0,0 +1,108 @@
1
+ import { hasSass, hasTailwind } from '@culur/utils-packages';
2
+ import { defineRule, defineRules } from '~/utils';
3
+
4
+ // https://github.com/hudochenkov/stylelint-order/blob/master/rules/order/README.md#options
5
+
6
+ type Keyword =
7
+ | 'custom-properties' /**/ // Custom properties (e.g. --property: 10px;)
8
+ | 'dollar-variables' /***/ // Dollar variables (e.g. $variable)
9
+ | 'at-variables' /*******/ // At-variables (e.g. @variable available in Less syntax)
10
+ | 'declarations' /*******/ // CSS declarations (e.g. display: block)
11
+ | 'rules' /**************/ // Nested rules (e.g. a { span {} })
12
+ | 'at-rules' /***********/ // Nested at-rules (e.g. div { @media () {} })
13
+ | 'less-mixins'; /*******/ // Mixins in Less syntax (e.g. .mixin();)
14
+
15
+ interface AtRule {
16
+ type: 'at-rule';
17
+ name?: string;
18
+ parameter?: string | RegExp;
19
+ hasBlock?: boolean;
20
+ }
21
+
22
+ interface Rule {
23
+ type: 'rule';
24
+ selector?: string | RegExp;
25
+ name?: string;
26
+ }
27
+
28
+ type PrimaryOption = Array<Keyword | AtRule | Rule>;
29
+ interface SecondaryOptions {
30
+ unspecified?: 'top' | 'bottom' | 'ignore';
31
+ }
32
+
33
+ /**
34
+ * https://sass-lang.com/documentation/at-rules
35
+ */
36
+ const atRuleScssImport: PrimaryOption = [
37
+ { type: 'at-rule', name: 'use' },
38
+ { type: 'at-rule', name: 'forward' },
39
+ { type: 'at-rule', name: 'import' },
40
+ ];
41
+ const atRuleScss: PrimaryOption = [
42
+ { type: 'at-rule', name: 'function' },
43
+ { type: 'at-rule', name: 'mixin' },
44
+ { type: 'at-rule', name: 'extend' },
45
+ { type: 'at-rule', name: 'include' },
46
+ { type: 'at-rule', name: 'at-root' },
47
+ ];
48
+
49
+ const variables: PrimaryOption = [
50
+ 'dollar-variables', // $variable
51
+ 'at-variables', // @variable
52
+ 'custom-properties', // --property: 10px
53
+ ];
54
+
55
+ /**
56
+ * https://tailwindcss.com/docs/functions-and-directives
57
+ */
58
+ const atRuleTailwind: PrimaryOption = [
59
+ { type: 'at-rule', name: 'tailwind' },
60
+ { type: 'at-rule', name: 'layer' },
61
+ { type: 'at-rule', name: 'apply' },
62
+ { type: 'at-rule', name: 'config' },
63
+ ];
64
+
65
+ /**
66
+ * https://v2.tailwindcss.com/docs/functions-and-directives
67
+ *
68
+ * backwards compatible for v2
69
+ */
70
+ const atRuleTailwindV2: PrimaryOption = [
71
+ { type: 'at-rule', name: 'variants' },
72
+ { type: 'at-rule', name: 'responsive' },
73
+ { type: 'at-rule', name: 'screen' },
74
+ ];
75
+
76
+ /**
77
+ * Nested rules (e.g. a { span {} })
78
+ */
79
+ const rules: PrimaryOption = [
80
+ {
81
+ type: 'rule',
82
+ selector: /^&::[\w-]+/,
83
+ },
84
+ 'rules',
85
+ ];
86
+
87
+ export const orderRule = () =>
88
+ defineRule<PrimaryOption, SecondaryOptions>([
89
+ [
90
+ ...(hasSass() ? atRuleScssImport : []),
91
+ ...(hasTailwind() ? atRuleTailwind : []),
92
+ ...variables,
93
+ ...(hasSass() ? atRuleScss : []),
94
+ ...(hasTailwind() ? atRuleTailwindV2 : []),
95
+ 'at-rules',
96
+ 'less-mixins',
97
+ 'declarations',
98
+ ...rules,
99
+ ],
100
+ {
101
+ unspecified: 'bottom',
102
+ },
103
+ ]);
104
+
105
+ export const order = () =>
106
+ defineRules({
107
+ 'order/order': orderRule(),
108
+ });
@@ -0,0 +1 @@
1
+ export * from './utils';
@@ -0,0 +1,46 @@
1
+ import { assert, describe, expect, expectTypeOf, it } from 'vitest';
2
+ import { defineRule, defineRules, mergeConfigs } from './utils';
3
+
4
+ describe('rules', () => {
5
+ it('defineRule & rules', () => {
6
+ type PrimaryOption = true;
7
+ interface SecondaryOptions {
8
+ ignore: ['comments'];
9
+ }
10
+
11
+ const blockNoEmptyRule = defineRule<PrimaryOption, SecondaryOptions>([
12
+ true,
13
+ { ignore: ['comments'] },
14
+ ]);
15
+ const blockNoEmpty = defineRules({ 'block-no-empty': blockNoEmptyRule });
16
+
17
+ assert(Array.isArray(blockNoEmptyRule));
18
+ expectTypeOf(blockNoEmpty).toBeObject();
19
+ });
20
+ });
21
+
22
+ describe('merge configs', () => {
23
+ it('should merge plugins', () => {
24
+ const mergedConfigs = mergeConfigs(
25
+ { plugins: ['plugin-a'] }, //
26
+ { plugins: ['plugin-b'] },
27
+ { plugins: [] },
28
+ {},
29
+ );
30
+
31
+ assert(Array.isArray(mergedConfigs.plugins));
32
+ expect(mergedConfigs.plugins).toHaveLength(2);
33
+ });
34
+
35
+ it('should merge overrides', () => {
36
+ const mergedConfigs = mergeConfigs(
37
+ { overrides: [{ files: 'a.css' }] },
38
+ { overrides: [{ files: 'b.css' }] },
39
+ { overrides: [] },
40
+ {},
41
+ );
42
+
43
+ assert(Array.isArray(mergedConfigs.overrides));
44
+ expect(mergedConfigs.overrides).toHaveLength(2);
45
+ });
46
+ });
@@ -0,0 +1,44 @@
1
+ import type { Config, ConfigRuleSettings } from 'stylelint';
2
+
3
+ export type ConfigRulePrimaryOption =
4
+ | boolean
5
+ | number
6
+ | string
7
+ | Array<boolean | number | string | object>;
8
+ export type ConfigRuleSecondaryOptions = object;
9
+
10
+ export const defineRule = <
11
+ PrimaryOption extends ConfigRulePrimaryOption,
12
+ SecondaryOptions extends ConfigRuleSecondaryOptions,
13
+ >(
14
+ rule: ConfigRuleSettings<PrimaryOption, SecondaryOptions>,
15
+ ) => rule;
16
+
17
+ export const defineRules = <TRules extends Config['rules']>(rule: TRules) =>
18
+ rule;
19
+
20
+ // clone function mergeConfigs from stylelint repository
21
+ export const mergeConfigs = (
22
+ ...configsOrNull: (Config | undefined | null | false)[]
23
+ ) => {
24
+ const configs = configsOrNull.filter((c): c is Config => !!c);
25
+
26
+ return {
27
+ plugins: [...new Set(configs.flatMap(c => c.plugins ?? []))],
28
+ overrides: [...new Set(configs.flatMap(c => c.overrides ?? []))],
29
+ extends: configs
30
+ .flatMap(c => c.extends ?? [])
31
+ .filter((item, index, arr) => arr.lastIndexOf(item) === index),
32
+ rules: configs.reduce(
33
+ (acc, config) => ({ ...acc, ...config.rules }),
34
+ {} as Config['rules'],
35
+ ),
36
+ ...configs.reduce(
37
+ (
38
+ acc,
39
+ { plugins: _p, overrides: _o, extends: _e, rules: _r, ...config },
40
+ ) => ({ ...acc, ...config }),
41
+ {} as Config,
42
+ ),
43
+ };
44
+ };