@d-zero/stylelint-rules 5.0.0-alpha.63 → 5.0.0-alpha.64

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.
@@ -16,6 +16,9 @@ export default createRule({
16
16
  const basename = ['.scss', '.sass'].includes(ext)
17
17
  ? originalBasename.replace(/^_/, '')
18
18
  : originalBasename;
19
+ // CSSファイルの場合は自動的にallowMultipleSelectorsをtrueにする
20
+ const isCssFile = ext === '.css';
21
+ const effectiveAllowMultipleSelectors = isCssFile || allowMultipleSelectors;
19
22
  const rules = root.nodes.filter((node) => node.type === 'rule');
20
23
  const [firstRule, ...overleftRules] = rules;
21
24
  for (const rule of overleftRules) {
@@ -45,20 +48,30 @@ export default createRule({
45
48
  if (!firstSelector) {
46
49
  throw new Error('Do not have a selector');
47
50
  }
51
+ // コンポーネントクラスの検証
52
+ let hasValidComponentClass = false;
48
53
  for (const node of firstSelector.nodes) {
49
54
  if (node.type === 'class') {
50
55
  const className = node.value;
51
- if (className !== basename) {
52
- stylelint.utils.report({
53
- result,
54
- ruleName,
55
- message: 'クラス名がファイル名と一致しません',
56
- node: firstRule,
57
- });
56
+ // 完全一致またはBEM形式(__で始まる)のチェック
57
+ if (className === basename ||
58
+ (isCssFile && className.startsWith(`${basename}__`))) {
59
+ hasValidComponentClass = true;
60
+ break;
58
61
  }
59
62
  }
60
63
  }
61
- if (!allowMultipleSelectors && multipleSelectors.length > 0) {
64
+ if (!hasValidComponentClass) {
65
+ stylelint.utils.report({
66
+ result,
67
+ ruleName,
68
+ message: isCssFile
69
+ ? `クラス名がファイル名と一致しないか、コンポーネント命名規則(${basename}__)で始まっていません`
70
+ : 'クラス名がファイル名と一致しません',
71
+ node: firstRule,
72
+ });
73
+ }
74
+ if (!effectiveAllowMultipleSelectors && multipleSelectors.length > 0) {
62
75
  stylelint.utils.report({
63
76
  result,
64
77
  ruleName,
@@ -38,18 +38,99 @@ describe('Exact Match', () => {
38
38
  line: 1,
39
39
  rule: '@d-zero/component',
40
40
  severity: 'error',
41
- text: 'クラス名がファイル名と一致しません',
41
+ text: 'クラス名がファイル名と一致しないか、コンポーネント命名規則(test__)で始まっていません',
42
42
  url: undefined,
43
43
  fix: undefined,
44
44
  },
45
+ ]);
46
+ });
47
+ });
48
+ describe('Component Naming Convention for CSS', () => {
49
+ test('exact match in CSS', async () => {
50
+ const {
51
+ // @ts-ignore
52
+ results: [{ warnings, parseErrors }], } = await lint({
53
+ codeFilename: 'button.css',
54
+ code: '.button { color: currentColor; }',
55
+ config: config({}),
56
+ });
57
+ expect(parseErrors).toHaveLength(0);
58
+ expect(warnings).toHaveLength(0);
59
+ });
60
+ test('component element match in CSS', async () => {
61
+ const {
62
+ // @ts-ignore
63
+ results: [{ warnings, parseErrors }], } = await lint({
64
+ codeFilename: 'button.css',
65
+ code: '.button__text { color: currentColor; }',
66
+ config: config({}),
67
+ });
68
+ expect(parseErrors).toHaveLength(0);
69
+ expect(warnings).toHaveLength(0);
70
+ });
71
+ test('multiple component elements in CSS (auto-allow)', async () => {
72
+ const {
73
+ // @ts-ignore
74
+ results: [{ warnings, parseErrors }], } = await lint({
75
+ codeFilename: 'button.css',
76
+ code: '.button__text, .button__icon { color: currentColor; }',
77
+ config: config({}),
78
+ });
79
+ expect(parseErrors).toHaveLength(0);
80
+ expect(warnings).toHaveLength(0);
81
+ });
82
+ test('mixed component and component elements in CSS', async () => {
83
+ const {
84
+ // @ts-ignore
85
+ results: [{ warnings, parseErrors }], } = await lint({
86
+ codeFilename: 'button.css',
87
+ code: '.button, .button__text, .button__icon { color: currentColor; }',
88
+ config: config({}),
89
+ });
90
+ expect(parseErrors).toHaveLength(0);
91
+ expect(warnings).toHaveLength(0);
92
+ });
93
+ test('invalid component naming convention in CSS', async () => {
94
+ const {
95
+ // @ts-ignore
96
+ results: [{ warnings, parseErrors }], } = await lint({
97
+ codeFilename: 'button.css',
98
+ code: '.card__text { color: currentColor; }',
99
+ config: config({}),
100
+ });
101
+ expect(parseErrors).toHaveLength(0);
102
+ expect(warnings).toStrictEqual([
45
103
  {
46
104
  column: 1,
47
- endColumn: 70,
105
+ endColumn: 37,
48
106
  endLine: 1,
49
107
  line: 1,
50
108
  rule: '@d-zero/component',
51
109
  severity: 'error',
52
- text: 'セレクタの定義は1つだけです',
110
+ text: 'クラス名がファイル名と一致しないか、コンポーネント命名規則(button__)で始まっていません',
111
+ url: undefined,
112
+ fix: undefined,
113
+ },
114
+ ]);
115
+ });
116
+ test('CSS with wrong component name', async () => {
117
+ const {
118
+ // @ts-ignore
119
+ results: [{ warnings, parseErrors }], } = await lint({
120
+ codeFilename: 'button.css',
121
+ code: '.card { color: currentColor; }',
122
+ config: config({}),
123
+ });
124
+ expect(parseErrors).toHaveLength(0);
125
+ expect(warnings).toStrictEqual([
126
+ {
127
+ column: 1,
128
+ endColumn: 31,
129
+ endLine: 1,
130
+ line: 1,
131
+ rule: '@d-zero/component',
132
+ severity: 'error',
133
+ text: 'クラス名がファイル名と一致しないか、コンポーネント命名規則(button__)で始まっていません',
53
134
  url: undefined,
54
135
  fix: undefined,
55
136
  },
@@ -70,7 +151,7 @@ describe('Partial Name', () => {
70
151
  });
71
152
  });
72
153
  describe('Options', () => {
73
- test('allowMultipleSelectors: false', async () => {
154
+ test('allowMultipleSelectors: false in SCSS', async () => {
74
155
  const {
75
156
  // @ts-ignore
76
157
  results: [{ warnings, parseErrors }], } = await lint({
@@ -81,7 +162,7 @@ describe('Options', () => {
81
162
  expect(parseErrors).toHaveLength(0);
82
163
  expect(warnings).toHaveLength(1);
83
164
  });
84
- test('allowMultipleSelectors: true', async () => {
165
+ test('allowMultipleSelectors: true in SCSS', async () => {
85
166
  const {
86
167
  // @ts-ignore
87
168
  results: [{ warnings, parseErrors }], } = await lint({
@@ -94,4 +175,17 @@ describe('Options', () => {
94
175
  expect(parseErrors).toHaveLength(0);
95
176
  expect(warnings).toHaveLength(0);
96
177
  });
178
+ test('CSS files automatically allow multiple selectors', async () => {
179
+ const {
180
+ // @ts-ignore
181
+ results: [{ warnings, parseErrors }], } = await lint({
182
+ codeFilename: 'button.css',
183
+ code: '.button, .button__text, .button__icon { color: currentColor; }',
184
+ config: config({
185
+ allowMultipleSelectors: false, // この設定は CSS では無視される
186
+ }),
187
+ });
188
+ expect(parseErrors).toHaveLength(0);
189
+ expect(warnings).toHaveLength(0);
190
+ });
97
191
  });
@@ -1,4 +1,4 @@
1
- import postcss from 'postcss';
1
+ import { parse } from 'postcss';
2
2
  import { describe, test, expect } from 'vitest';
3
3
  import { getValueType } from './get-value-type.js';
4
4
  /**
@@ -6,7 +6,7 @@ import { getValueType } from './get-value-type.js';
6
6
  * @param css
7
7
  */
8
8
  function p(css) {
9
- const root = postcss.parse(css);
9
+ const root = parse(css);
10
10
  const rule = root.first;
11
11
  const decl = rule.first;
12
12
  const nodeWithType = getValueType(decl);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d-zero/stylelint-rules",
3
- "version": "5.0.0-alpha.63",
3
+ "version": "5.0.0-alpha.64",
4
4
  "description": "Rules of Stylelint for D-ZERO",
5
5
  "repository": "https://github.com/d-zero-dev/linters.git",
6
6
  "author": "D-ZERO Co., Ltd.",
@@ -23,14 +23,14 @@
23
23
  "build": "tsc"
24
24
  },
25
25
  "dependencies": {
26
- "@d-zero/csstree-scss-syntax": "5.0.0-alpha.63",
26
+ "@d-zero/csstree-scss-syntax": "5.0.0-alpha.64",
27
27
  "css-tree": "3.1.0",
28
28
  "postcss-selector-parser": "7.1.0",
29
29
  "postcss-value-parser": "4.2.0",
30
- "stylelint": "16.19.1"
30
+ "stylelint": "16.20.0"
31
31
  },
32
32
  "devDependencies": {
33
- "postcss": "8.5.3"
33
+ "postcss": "8.5.5"
34
34
  },
35
- "gitHead": "7bc8706d98d3eab1e1ed84b57454f441a4d016ec"
35
+ "gitHead": "9efcc82edcd2392e49218ef22ce3831f61ba566c"
36
36
  }