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

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,15 +16,21 @@ 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
- for (const rule of overleftRules) {
22
- stylelint.utils.report({
23
- result,
24
- ruleName,
25
- message: '1つのファイルに定義できるコンポーネントクラスは1つだけです',
26
- node: rule,
27
- });
24
+ // allowMultipleSelectorsに基づいて複数ルール制約をチェック
25
+ if (!effectiveAllowMultipleSelectors && overleftRules.length > 0) {
26
+ for (const rule of overleftRules) {
27
+ stylelint.utils.report({
28
+ result,
29
+ ruleName,
30
+ message: '1つのファイルに定義できるコンポーネントクラスは1つだけです',
31
+ node: rule,
32
+ });
33
+ }
28
34
  }
29
35
  if (!firstRule) {
30
36
  stylelint.utils.report({
@@ -35,36 +41,48 @@ export default createRule({
35
41
  });
36
42
  return;
37
43
  }
38
- const selectors = [];
39
- selectorParser((parsedRoot) => {
40
- for (const node of parsedRoot.nodes) {
41
- selectors.push(node);
42
- }
43
- }).processSync(firstRule.selector);
44
- const [firstSelector, ...multipleSelectors] = selectors;
45
- if (!firstSelector) {
46
- throw new Error('Do not have a selector');
47
- }
48
- for (const node of firstSelector.nodes) {
49
- if (node.type === 'class') {
50
- const className = node.value;
51
- if (className !== basename) {
52
- stylelint.utils.report({
53
- result,
54
- ruleName,
55
- message: 'クラス名がファイル名と一致しません',
56
- node: firstRule,
57
- });
44
+ // 全ルールのコンポーネントクラスを検証
45
+ for (const rule of rules) {
46
+ const ruleSelectors = [];
47
+ selectorParser((parsedRoot) => {
48
+ for (const node of parsedRoot.nodes) {
49
+ ruleSelectors.push(node);
50
+ }
51
+ }).processSync(rule.selector);
52
+ const [ruleFirstSelector, ...ruleMultipleSelectors] = ruleSelectors;
53
+ if (!ruleFirstSelector)
54
+ continue;
55
+ let hasValidComponentClass = false;
56
+ for (const node of ruleFirstSelector.nodes) {
57
+ if (node.type === 'class') {
58
+ const className = node.value;
59
+ // 完全一致またはコンポーネント命名規則(__で始まる)のチェック
60
+ if (className === basename ||
61
+ (isCssFile && className.startsWith(`${basename}__`))) {
62
+ hasValidComponentClass = true;
63
+ break;
64
+ }
58
65
  }
59
66
  }
60
- }
61
- if (!allowMultipleSelectors && multipleSelectors.length > 0) {
62
- stylelint.utils.report({
63
- result,
64
- ruleName,
65
- message: 'セレクタの定義は1つだけです',
66
- node: firstRule,
67
- });
67
+ if (!hasValidComponentClass) {
68
+ stylelint.utils.report({
69
+ result,
70
+ ruleName,
71
+ message: isCssFile
72
+ ? `クラス名がファイル名と一致しないか、コンポーネント命名規則(${basename}__)で始まっていません`
73
+ : 'クラス名がファイル名と一致しません',
74
+ node: rule,
75
+ });
76
+ }
77
+ // 複数セレクタのチェック
78
+ if (!effectiveAllowMultipleSelectors && ruleMultipleSelectors.length > 0) {
79
+ stylelint.utils.report({
80
+ result,
81
+ ruleName,
82
+ message: 'セレクタの定義は1つだけです',
83
+ node: rule,
84
+ });
85
+ }
68
86
  }
69
87
  };
70
88
  },
@@ -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,46 @@ 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
+ });
191
+ test('CSS files allow multiple rules', async () => {
192
+ const {
193
+ // @ts-ignore
194
+ results: [{ warnings, parseErrors }], } = await lint({
195
+ codeFilename: 'button.css',
196
+ code: `.button { color: currentColor; }
197
+ .button__text { font-size: 14px; }
198
+ .button__icon { width: 16px; }`,
199
+ config: config({}),
200
+ });
201
+ expect(parseErrors).toHaveLength(0);
202
+ expect(warnings).toHaveLength(0);
203
+ });
204
+ test('CSS files reject multiple components', async () => {
205
+ const {
206
+ // @ts-ignore
207
+ results: [{ warnings, parseErrors }], } = await lint({
208
+ codeFilename: 'c-component.css',
209
+ code: `.c-component { --prop: value; }
210
+ .c-component__element { --prop: value; }
211
+ .c-component2 { --prop: value; }
212
+ .c-specific { --prop: value; }`,
213
+ config: config({}),
214
+ });
215
+ expect(parseErrors).toHaveLength(0);
216
+ expect(warnings).toHaveLength(2);
217
+ expect(warnings[0].text).toBe('クラス名がファイル名と一致しないか、コンポーネント命名規則(c-component__)で始まっていません');
218
+ expect(warnings[1].text).toBe('クラス名がファイル名と一致しないか、コンポーネント命名規則(c-component__)で始まっていません');
219
+ });
97
220
  });
@@ -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.65",
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.65",
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": "884a176442f45e717290664c2b4b1912d8055b0c"
36
36
  }