happy-css-modules 3.0.1 → 3.1.0
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/package.json +1 -1
- package/src/emitter/dts.test.ts +3 -3
- package/src/emitter/dts.ts +59 -37
- package/src/emitter/index.test.ts +3 -3
- package/src/locator/index.test.ts +143 -48
- package/src/locator/index.ts +51 -23
- package/src/locator/postcss.test.ts +135 -26
- package/src/locator/postcss.ts +120 -36
- package/src/test-util/util.ts +23 -18
- package/src/transformer/less-transformer.test.ts +25 -15
- package/src/transformer/postcss-transformer.test.ts +5 -3
- package/src/transformer/scss-transformer.test.ts +38 -19
- package/dist/cli.d.ts +0 -6
- package/dist/cli.js +0 -121
- package/dist/cli.js.map +0 -1
- package/dist/cli.test.d.ts +0 -1
- package/dist/cli.test.js +0 -66
- package/dist/cli.test.js.map +0 -1
- package/dist/config.d.ts +0 -1
- package/dist/config.js +0 -2
- package/dist/config.js.map +0 -1
- package/dist/emitter/dts.d.ts +0 -14
- package/dist/emitter/dts.js +0 -111
- package/dist/emitter/dts.js.map +0 -1
- package/dist/emitter/dts.test.d.ts +0 -1
- package/dist/emitter/dts.test.js +0 -205
- package/dist/emitter/dts.test.js.map +0 -1
- package/dist/emitter/file-system.d.ts +0 -6
- package/dist/emitter/file-system.js +0 -26
- package/dist/emitter/file-system.js.map +0 -1
- package/dist/emitter/file-system.test.d.ts +0 -1
- package/dist/emitter/file-system.test.js +0 -34
- package/dist/emitter/file-system.test.js.map +0 -1
- package/dist/emitter/index.d.ts +0 -34
- package/dist/emitter/index.js +0 -49
- package/dist/emitter/index.js.map +0 -1
- package/dist/emitter/index.test.d.ts +0 -1
- package/dist/emitter/index.test.js +0 -68
- package/dist/emitter/index.test.js.map +0 -1
- package/dist/emitter/source-map.d.ts +0 -8
- package/dist/emitter/source-map.js +0 -16
- package/dist/emitter/source-map.js.map +0 -1
- package/dist/emitter/source-map.test.d.ts +0 -1
- package/dist/emitter/source-map.test.js +0 -13
- package/dist/emitter/source-map.test.js.map +0 -1
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -6
- package/dist/index.js.map +0 -1
- package/dist/integration-test/go-to-definition.test.d.ts +0 -1
- package/dist/integration-test/go-to-definition.test.js +0 -334
- package/dist/integration-test/go-to-definition.test.js.map +0 -1
- package/dist/library/source-map/index.d.ts +0 -8
- package/dist/library/source-map/index.js +0 -5
- package/dist/library/source-map/index.js.map +0 -1
- package/dist/locator/index.d.ts +0 -44
- package/dist/locator/index.js +0 -147
- package/dist/locator/index.js.map +0 -1
- package/dist/locator/index.test.d.ts +0 -1
- package/dist/locator/index.test.js +0 -299
- package/dist/locator/index.test.js.map +0 -1
- package/dist/locator/postcss.d.ts +0 -53
- package/dist/locator/postcss.js +0 -169
- package/dist/locator/postcss.js.map +0 -1
- package/dist/locator/postcss.test.d.ts +0 -1
- package/dist/locator/postcss.test.js +0 -196
- package/dist/locator/postcss.test.js.map +0 -1
- package/dist/logger.d.ts +0 -9
- package/dist/logger.js +0 -28
- package/dist/logger.js.map +0 -1
- package/dist/regression-test/issue-168.test.d.ts +0 -1
- package/dist/regression-test/issue-168.test.js +0 -30
- package/dist/regression-test/issue-168.test.js.map +0 -1
- package/dist/resolver/index.d.ts +0 -22
- package/dist/resolver/index.js +0 -44
- package/dist/resolver/index.js.map +0 -1
- package/dist/resolver/index.test.d.ts +0 -1
- package/dist/resolver/index.test.js +0 -16
- package/dist/resolver/index.test.js.map +0 -1
- package/dist/resolver/node-resolver.d.ts +0 -2
- package/dist/resolver/node-resolver.js +0 -6
- package/dist/resolver/node-resolver.js.map +0 -1
- package/dist/resolver/node-resolver.test.d.ts +0 -1
- package/dist/resolver/node-resolver.test.js +0 -25
- package/dist/resolver/node-resolver.test.js.map +0 -1
- package/dist/resolver/relative-resolver.d.ts +0 -2
- package/dist/resolver/relative-resolver.js +0 -5
- package/dist/resolver/relative-resolver.js.map +0 -1
- package/dist/resolver/relative-resolver.test.d.ts +0 -1
- package/dist/resolver/relative-resolver.test.js +0 -12
- package/dist/resolver/relative-resolver.test.js.map +0 -1
- package/dist/resolver/webpack-resolver.d.ts +0 -24
- package/dist/resolver/webpack-resolver.js +0 -91
- package/dist/resolver/webpack-resolver.js.map +0 -1
- package/dist/resolver/webpack-resolver.test.d.ts +0 -1
- package/dist/resolver/webpack-resolver.test.js +0 -89
- package/dist/resolver/webpack-resolver.test.js.map +0 -1
- package/dist/runner.d.ts +0 -76
- package/dist/runner.js +0 -120
- package/dist/runner.js.map +0 -1
- package/dist/runner.test.d.ts +0 -1
- package/dist/runner.test.js +0 -287
- package/dist/runner.test.js.map +0 -1
- package/dist/test-util/jest/resolver.cjs +0 -31
- package/dist/test-util/jest/resolver.cjs.map +0 -1
- package/dist/test-util/jest/resolver.d.cts +0 -16
- package/dist/test-util/tsserver.d.ts +0 -31
- package/dist/test-util/tsserver.js +0 -112
- package/dist/test-util/tsserver.js.map +0 -1
- package/dist/test-util/util.d.ts +0 -28
- package/dist/test-util/util.js +0 -81
- package/dist/test-util/util.js.map +0 -1
- package/dist/transformer/index.d.ts +0 -30
- package/dist/transformer/index.js +0 -25
- package/dist/transformer/index.js.map +0 -1
- package/dist/transformer/index.test.d.ts +0 -1
- package/dist/transformer/index.test.js +0 -66
- package/dist/transformer/index.test.js.map +0 -1
- package/dist/transformer/less-transformer.d.ts +0 -2
- package/dist/transformer/less-transformer.js +0 -45
- package/dist/transformer/less-transformer.js.map +0 -1
- package/dist/transformer/less-transformer.test.d.ts +0 -1
- package/dist/transformer/less-transformer.test.js +0 -124
- package/dist/transformer/less-transformer.test.js.map +0 -1
- package/dist/transformer/postcss-transformer.d.ts +0 -12
- package/dist/transformer/postcss-transformer.js +0 -32
- package/dist/transformer/postcss-transformer.js.map +0 -1
- package/dist/transformer/postcss-transformer.test.d.ts +0 -1
- package/dist/transformer/postcss-transformer.test.js +0 -176
- package/dist/transformer/postcss-transformer.test.js.map +0 -1
- package/dist/transformer/scss-transformer.d.ts +0 -2
- package/dist/transformer/scss-transformer.js +0 -40
- package/dist/transformer/scss-transformer.js.map +0 -1
- package/dist/transformer/scss-transformer.test.d.ts +0 -1
- package/dist/transformer/scss-transformer.test.js +0 -130
- package/dist/transformer/scss-transformer.test.js.map +0 -1
- package/dist/util.d.ts +0 -21
- package/dist/util.js +0 -72
- package/dist/util.js.map +0 -1
- package/dist/util.test.d.ts +0 -1
- package/dist/util.test.js +0 -74
- package/dist/util.test.js.map +0 -1
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import dedent from 'dedent';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import type { AtRule } from 'postcss';
|
|
3
|
+
import {
|
|
4
|
+
createRoot,
|
|
5
|
+
createClassSelectors,
|
|
6
|
+
createAtImports,
|
|
7
|
+
createFixtures,
|
|
8
|
+
createAtValues,
|
|
9
|
+
} from '../test-util/util.js';
|
|
10
|
+
import {
|
|
11
|
+
generateLocalTokenNames,
|
|
12
|
+
getOriginalLocationOfClassSelector,
|
|
13
|
+
parseAtImport,
|
|
14
|
+
parseAtValue,
|
|
15
|
+
collectNodes,
|
|
16
|
+
getOriginalLocationOfAtValue,
|
|
17
|
+
} from './postcss.js';
|
|
4
18
|
|
|
5
19
|
describe('generateLocalTokenNames', () => {
|
|
6
20
|
test('basic', async () => {
|
|
@@ -27,9 +41,11 @@ describe('generateLocalTokenNames', () => {
|
|
|
27
41
|
.local_class_name_3 {}
|
|
28
42
|
}
|
|
29
43
|
:local(.local_class_name_4) {}
|
|
44
|
+
@value value: #BF4040;
|
|
30
45
|
`),
|
|
31
46
|
),
|
|
32
47
|
).toStrictEqual([
|
|
48
|
+
'value',
|
|
33
49
|
'basic',
|
|
34
50
|
'cascading',
|
|
35
51
|
'pseudo_class_1',
|
|
@@ -63,14 +79,18 @@ describe('generateLocalTokenNames', () => {
|
|
|
63
79
|
).toStrictEqual([]);
|
|
64
80
|
});
|
|
65
81
|
test('does not track styles imported by @value in other file because it is not a local token', async () => {
|
|
66
|
-
createFixtures({
|
|
82
|
+
createFixtures({
|
|
83
|
+
'/test/1.css': dedent`
|
|
84
|
+
.a {}
|
|
85
|
+
`,
|
|
86
|
+
});
|
|
67
87
|
expect(
|
|
68
88
|
await generateLocalTokenNames(
|
|
69
89
|
createRoot(`
|
|
70
|
-
@value
|
|
90
|
+
@value a from "/test/1.css";
|
|
71
91
|
`),
|
|
72
92
|
),
|
|
73
|
-
).toStrictEqual([]);
|
|
93
|
+
).toStrictEqual(['a']);
|
|
74
94
|
});
|
|
75
95
|
test('does not track styles imported by composes in other file because it is not a local token', async () => {
|
|
76
96
|
createFixtures({
|
|
@@ -90,14 +110,14 @@ describe('generateLocalTokenNames', () => {
|
|
|
90
110
|
});
|
|
91
111
|
});
|
|
92
112
|
|
|
93
|
-
describe('
|
|
113
|
+
describe('getOriginalLocationOfClassSelector', () => {
|
|
94
114
|
test('basic', () => {
|
|
95
115
|
const [basic] = createClassSelectors(
|
|
96
116
|
createRoot(dedent`
|
|
97
117
|
.basic {}
|
|
98
118
|
`),
|
|
99
119
|
);
|
|
100
|
-
expect(
|
|
120
|
+
expect(getOriginalLocationOfClassSelector(basic!.rule, basic!.classSelector)).toMatchInlineSnapshot(
|
|
101
121
|
`{ filePath: "/test/test.css", start: { line: 1, column: 1 }, end: { line: 1, column: 6 } }`,
|
|
102
122
|
);
|
|
103
123
|
});
|
|
@@ -109,10 +129,10 @@ describe('getOriginalLocation', () => {
|
|
|
109
129
|
.cascading {}
|
|
110
130
|
`),
|
|
111
131
|
);
|
|
112
|
-
expect(
|
|
132
|
+
expect(getOriginalLocationOfClassSelector(cascading_1!.rule, cascading_1!.classSelector)).toMatchInlineSnapshot(
|
|
113
133
|
`{ filePath: "/test/test.css", start: { line: 1, column: 1 }, end: { line: 1, column: 10 } }`,
|
|
114
134
|
);
|
|
115
|
-
expect(
|
|
135
|
+
expect(getOriginalLocationOfClassSelector(cascading_2!.rule, cascading_2!.classSelector)).toMatchInlineSnapshot(
|
|
116
136
|
`{ filePath: "/test/test.css", start: { line: 2, column: 1 }, end: { line: 2, column: 10 } }`,
|
|
117
137
|
);
|
|
118
138
|
});
|
|
@@ -125,13 +145,19 @@ describe('getOriginalLocation', () => {
|
|
|
125
145
|
:not(.pseudo_class_3) {}
|
|
126
146
|
`),
|
|
127
147
|
);
|
|
128
|
-
expect(
|
|
148
|
+
expect(
|
|
149
|
+
getOriginalLocationOfClassSelector(pseudo_class_1!.rule, pseudo_class_1!.classSelector),
|
|
150
|
+
).toMatchInlineSnapshot(
|
|
129
151
|
`{ filePath: "/test/test.css", start: { line: 1, column: 1 }, end: { line: 1, column: 15 } }`,
|
|
130
152
|
);
|
|
131
|
-
expect(
|
|
153
|
+
expect(
|
|
154
|
+
getOriginalLocationOfClassSelector(pseudo_class_2!.rule, pseudo_class_2!.classSelector),
|
|
155
|
+
).toMatchInlineSnapshot(
|
|
132
156
|
`{ filePath: "/test/test.css", start: { line: 2, column: 1 }, end: { line: 2, column: 15 } }`,
|
|
133
157
|
);
|
|
134
|
-
expect(
|
|
158
|
+
expect(
|
|
159
|
+
getOriginalLocationOfClassSelector(pseudo_class_3!.rule, pseudo_class_3!.classSelector),
|
|
160
|
+
).toMatchInlineSnapshot(
|
|
135
161
|
`{ filePath: "/test/test.css", start: { line: 3, column: 6 }, end: { line: 3, column: 20 } }`,
|
|
136
162
|
);
|
|
137
163
|
});
|
|
@@ -142,10 +168,14 @@ describe('getOriginalLocation', () => {
|
|
|
142
168
|
.multiple_selector_1.multiple_selector_2 {}
|
|
143
169
|
`),
|
|
144
170
|
);
|
|
145
|
-
expect(
|
|
171
|
+
expect(
|
|
172
|
+
getOriginalLocationOfClassSelector(multiple_selector_1!.rule, multiple_selector_1!.classSelector),
|
|
173
|
+
).toMatchInlineSnapshot(
|
|
146
174
|
`{ filePath: "/test/test.css", start: { line: 1, column: 1 }, end: { line: 1, column: 20 } }`,
|
|
147
175
|
);
|
|
148
|
-
expect(
|
|
176
|
+
expect(
|
|
177
|
+
getOriginalLocationOfClassSelector(multiple_selector_2!.rule, multiple_selector_2!.classSelector),
|
|
178
|
+
).toMatchInlineSnapshot(
|
|
149
179
|
`{ filePath: "/test/test.css", start: { line: 1, column: 21 }, end: { line: 1, column: 40 } }`,
|
|
150
180
|
);
|
|
151
181
|
});
|
|
@@ -157,10 +187,10 @@ describe('getOriginalLocation', () => {
|
|
|
157
187
|
.combinator_1 + .combinator_2 {}
|
|
158
188
|
`),
|
|
159
189
|
);
|
|
160
|
-
expect(
|
|
190
|
+
expect(getOriginalLocationOfClassSelector(combinator_1!.rule, combinator_1!.classSelector)).toMatchInlineSnapshot(
|
|
161
191
|
`{ filePath: "/test/test.css", start: { line: 1, column: 1 }, end: { line: 1, column: 13 } }`,
|
|
162
192
|
);
|
|
163
|
-
expect(
|
|
193
|
+
expect(getOriginalLocationOfClassSelector(combinator_2!.rule, combinator_2!.classSelector)).toMatchInlineSnapshot(
|
|
164
194
|
`{ filePath: "/test/test.css", start: { line: 1, column: 17 }, end: { line: 1, column: 29 } }`,
|
|
165
195
|
);
|
|
166
196
|
});
|
|
@@ -175,7 +205,7 @@ describe('getOriginalLocation', () => {
|
|
|
175
205
|
}
|
|
176
206
|
`),
|
|
177
207
|
);
|
|
178
|
-
expect(
|
|
208
|
+
expect(getOriginalLocationOfClassSelector(at_rule!.rule, at_rule!.classSelector)).toMatchInlineSnapshot(
|
|
179
209
|
`{ filePath: "/test/test.css", start: { line: 3, column: 5 }, end: { line: 3, column: 12 } }`,
|
|
180
210
|
);
|
|
181
211
|
});
|
|
@@ -186,10 +216,14 @@ describe('getOriginalLocation', () => {
|
|
|
186
216
|
.selector_list_1, .selector_list_2 {}
|
|
187
217
|
`),
|
|
188
218
|
);
|
|
189
|
-
expect(
|
|
219
|
+
expect(
|
|
220
|
+
getOriginalLocationOfClassSelector(selector_list_1!.rule, selector_list_1!.classSelector),
|
|
221
|
+
).toMatchInlineSnapshot(
|
|
190
222
|
`{ filePath: "/test/test.css", start: { line: 1, column: 1 }, end: { line: 1, column: 16 } }`,
|
|
191
223
|
);
|
|
192
|
-
expect(
|
|
224
|
+
expect(
|
|
225
|
+
getOriginalLocationOfClassSelector(selector_list_2!.rule, selector_list_2!.classSelector),
|
|
226
|
+
).toMatchInlineSnapshot(
|
|
193
227
|
`{ filePath: "/test/test.css", start: { line: 1, column: 19 }, end: { line: 1, column: 34 } }`,
|
|
194
228
|
);
|
|
195
229
|
});
|
|
@@ -205,16 +239,24 @@ describe('getOriginalLocation', () => {
|
|
|
205
239
|
:local(.local_class_name_4) {}
|
|
206
240
|
`),
|
|
207
241
|
);
|
|
208
|
-
expect(
|
|
242
|
+
expect(
|
|
243
|
+
getOriginalLocationOfClassSelector(local_class_name_1!.rule, local_class_name_1!.classSelector),
|
|
244
|
+
).toMatchInlineSnapshot(
|
|
209
245
|
`{ filePath: "/test/test.css", start: { line: 1, column: 8 }, end: { line: 1, column: 26 } }`,
|
|
210
246
|
);
|
|
211
|
-
expect(
|
|
247
|
+
expect(
|
|
248
|
+
getOriginalLocationOfClassSelector(local_class_name_2!.rule, local_class_name_2!.classSelector),
|
|
249
|
+
).toMatchInlineSnapshot(
|
|
212
250
|
`{ filePath: "/test/test.css", start: { line: 3, column: 3 }, end: { line: 3, column: 21 } }`,
|
|
213
251
|
);
|
|
214
|
-
expect(
|
|
252
|
+
expect(
|
|
253
|
+
getOriginalLocationOfClassSelector(local_class_name_3!.rule, local_class_name_3!.classSelector),
|
|
254
|
+
).toMatchInlineSnapshot(
|
|
215
255
|
`{ filePath: "/test/test.css", start: { line: 4, column: 3 }, end: { line: 4, column: 21 } }`,
|
|
216
256
|
);
|
|
217
|
-
expect(
|
|
257
|
+
expect(
|
|
258
|
+
getOriginalLocationOfClassSelector(local_class_name_4!.rule, local_class_name_4!.classSelector),
|
|
259
|
+
).toMatchInlineSnapshot(
|
|
218
260
|
`{ filePath: "/test/test.css", start: { line: 6, column: 8 }, end: { line: 6, column: 26 } }`,
|
|
219
261
|
);
|
|
220
262
|
});
|
|
@@ -227,19 +269,44 @@ describe('getOriginalLocation', () => {
|
|
|
227
269
|
+ .with_newline_3, {}
|
|
228
270
|
`),
|
|
229
271
|
);
|
|
230
|
-
expect(
|
|
272
|
+
expect(
|
|
273
|
+
getOriginalLocationOfClassSelector(with_newline_1!.rule, with_newline_1!.classSelector),
|
|
274
|
+
).toMatchInlineSnapshot(
|
|
231
275
|
`{ filePath: "/test/test.css", start: { line: 1, column: 1 }, end: { line: 1, column: 15 } }`,
|
|
232
276
|
);
|
|
233
|
-
expect(
|
|
277
|
+
expect(
|
|
278
|
+
getOriginalLocationOfClassSelector(with_newline_2!.rule, with_newline_2!.classSelector),
|
|
279
|
+
).toMatchInlineSnapshot(
|
|
234
280
|
`{ filePath: "/test/test.css", start: { line: 2, column: 1 }, end: { line: 2, column: 15 } }`,
|
|
235
281
|
);
|
|
236
282
|
|
|
237
|
-
expect(
|
|
283
|
+
expect(
|
|
284
|
+
getOriginalLocationOfClassSelector(with_newline_3!.rule, with_newline_3!.classSelector),
|
|
285
|
+
).toMatchInlineSnapshot(
|
|
238
286
|
`{ filePath: "/test/test.css", start: { line: 3, column: 5 }, end: { line: 3, column: 19 } }`,
|
|
239
287
|
);
|
|
240
288
|
});
|
|
241
289
|
});
|
|
242
290
|
|
|
291
|
+
test('getOriginalLocationOfAtValue', () => {
|
|
292
|
+
function tryGetOriginalLocationOfAtValue(atValue: AtRule) {
|
|
293
|
+
const parsed = parseAtValue(atValue);
|
|
294
|
+
if (parsed.type === 'valueDeclaration') {
|
|
295
|
+
return getOriginalLocationOfAtValue(atValue, parsed);
|
|
296
|
+
} else {
|
|
297
|
+
throw new Error('Unexpected type');
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const [basic] = createAtValues(
|
|
301
|
+
createRoot(dedent`
|
|
302
|
+
@value basic: #000;
|
|
303
|
+
`),
|
|
304
|
+
);
|
|
305
|
+
expect(tryGetOriginalLocationOfAtValue(basic!)).toMatchInlineSnapshot(
|
|
306
|
+
`{ filePath: "/test/test.css", start: { line: 1, column: 8 }, end: { line: 1, column: 13 } }`,
|
|
307
|
+
);
|
|
308
|
+
});
|
|
309
|
+
|
|
243
310
|
test('collectNodes', () => {
|
|
244
311
|
const ast = createRoot(dedent`
|
|
245
312
|
@import;
|
|
@@ -277,3 +344,45 @@ test('parseAtImport', () => {
|
|
|
277
344
|
expect(parseAtImport(atImports[3]!)).toBe('test.css');
|
|
278
345
|
expect(parseAtImport(atImports[4]!)).toBe('test.css');
|
|
279
346
|
});
|
|
347
|
+
|
|
348
|
+
test('parseAtValue', () => {
|
|
349
|
+
const atValues = createAtValues(
|
|
350
|
+
createRoot(dedent`
|
|
351
|
+
@value basic: #000;
|
|
352
|
+
@value withoutColon #000;
|
|
353
|
+
@value empty:;
|
|
354
|
+
@value comment:/* comment */;
|
|
355
|
+
@value complex: (max-width: 599px);
|
|
356
|
+
@value import from "test.css";
|
|
357
|
+
@value import1, import2 from "test.css";
|
|
358
|
+
@value import as alias from "test.css";
|
|
359
|
+
/*
|
|
360
|
+
* NOTE: happy-css-modules intentionally does not support module specifier as variable.
|
|
361
|
+
* e.g. \`@value d, e from moduleName;\`
|
|
362
|
+
*/
|
|
363
|
+
`),
|
|
364
|
+
);
|
|
365
|
+
expect(parseAtValue(atValues[0]!)).toStrictEqual({ type: 'valueDeclaration', tokenName: 'basic' });
|
|
366
|
+
expect(parseAtValue(atValues[1]!)).toStrictEqual({ type: 'valueDeclaration', tokenName: 'withoutColon' });
|
|
367
|
+
expect(parseAtValue(atValues[2]!)).toStrictEqual({ type: 'valueDeclaration', tokenName: 'empty' });
|
|
368
|
+
expect(parseAtValue(atValues[3]!)).toStrictEqual({ type: 'valueDeclaration', tokenName: 'comment' });
|
|
369
|
+
expect(parseAtValue(atValues[4]!)).toStrictEqual({ type: 'valueDeclaration', tokenName: 'complex' });
|
|
370
|
+
expect(parseAtValue(atValues[5]!)).toStrictEqual({
|
|
371
|
+
type: 'valueImportDeclaration',
|
|
372
|
+
imports: [{ importedTokenName: 'import', localTokenName: 'import' }],
|
|
373
|
+
from: 'test.css',
|
|
374
|
+
});
|
|
375
|
+
expect(parseAtValue(atValues[6]!)).toStrictEqual({
|
|
376
|
+
type: 'valueImportDeclaration',
|
|
377
|
+
imports: [
|
|
378
|
+
{ importedTokenName: 'import1', localTokenName: 'import1' },
|
|
379
|
+
{ importedTokenName: 'import2', localTokenName: 'import2' },
|
|
380
|
+
],
|
|
381
|
+
from: 'test.css',
|
|
382
|
+
});
|
|
383
|
+
expect(parseAtValue(atValues[7]!)).toStrictEqual({
|
|
384
|
+
type: 'valueImportDeclaration',
|
|
385
|
+
imports: [{ importedTokenName: 'import', localTokenName: 'alias' }],
|
|
386
|
+
from: 'test.css',
|
|
387
|
+
});
|
|
388
|
+
});
|
package/src/locator/postcss.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import postcss, { type Rule, type AtRule, type Root, type Node
|
|
1
|
+
import postcss, { type Rule, type AtRule, type Root, type Node } from 'postcss';
|
|
2
2
|
import modules from 'postcss-modules';
|
|
3
3
|
import selectorParser, { type ClassName } from 'postcss-selector-parser';
|
|
4
4
|
import valueParser from 'postcss-value-parser';
|
|
@@ -26,40 +26,27 @@ export type Location =
|
|
|
26
26
|
end: undefined;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
function removeDependenciesPlugin(): Plugin {
|
|
30
|
-
return {
|
|
31
|
-
postcssPlugin: 'remove-dependencies',
|
|
32
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
33
|
-
AtRule(atRule) {
|
|
34
|
-
if (isAtImportNode(atRule) || isAtValueNode(atRule)) {
|
|
35
|
-
atRule.remove();
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
39
|
-
Declaration(declaration) {
|
|
40
|
-
if (isComposesDeclaration(declaration)) {
|
|
41
|
-
declaration.remove();
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
29
|
/**
|
|
48
30
|
* Traverses a local token from the AST and returns its name.
|
|
49
31
|
* @param ast The AST to traverse.
|
|
50
32
|
* @returns The name of the local token.
|
|
51
33
|
*/
|
|
52
34
|
export async function generateLocalTokenNames(ast: Root): Promise<string[]> {
|
|
35
|
+
class EmptyLoader {
|
|
36
|
+
async fetch(_file: string, _relativeTo: string, _depTrace: string): Promise<{ [key: string]: string }> {
|
|
37
|
+
// Return an empty object because we do not want to load external tokens in `generateLocalTokenNames`.
|
|
38
|
+
return Promise.resolve({});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
53
41
|
return new Promise((resolve, reject) => {
|
|
54
42
|
postcss
|
|
55
43
|
.default()
|
|
56
|
-
// postcss-modules collects tokens (i.e., includes external tokens) by following
|
|
57
|
-
// the dependencies specified in the @import.
|
|
58
|
-
// However, we do not want `generateLocalTokenNames` to return external tokens.
|
|
59
|
-
// So we remove the @import beforehand.
|
|
60
|
-
.use(removeDependenciesPlugin())
|
|
61
44
|
.use(
|
|
62
45
|
modules({
|
|
46
|
+
// `@import`, `@value`, and `composes` can read tokens from external files.
|
|
47
|
+
// However, we want to collect only local tokens. So we will fake that
|
|
48
|
+
// an empty token is exported from the external file.
|
|
49
|
+
Loader: EmptyLoader,
|
|
63
50
|
getJSON: (_cssFileName, json) => {
|
|
64
51
|
resolve(Object.keys(json));
|
|
65
52
|
},
|
|
@@ -72,12 +59,12 @@ export async function generateLocalTokenNames(ast: Root): Promise<string[]> {
|
|
|
72
59
|
}
|
|
73
60
|
|
|
74
61
|
/**
|
|
75
|
-
* Get the
|
|
62
|
+
* Get the original location of the class selector.
|
|
76
63
|
* @param rule The rule node that contains the token.
|
|
77
64
|
* @param classSelector The class selector node that contains the token.
|
|
78
|
-
* @returns The
|
|
65
|
+
* @returns The original location of the class selector.
|
|
79
66
|
*/
|
|
80
|
-
export function
|
|
67
|
+
export function getOriginalLocationOfClassSelector(rule: Rule, classSelector: ClassName): Location {
|
|
81
68
|
// The node derived from `postcss.parse` always has `source` property. Therefore, this line is unreachable.
|
|
82
69
|
if (rule.source === undefined || classSelector.source === undefined) throw new Error('Node#source is undefined');
|
|
83
70
|
// The node derived from `postcss.parse` always has `start` and `end` property. Therefore, this line is unreachable.
|
|
@@ -127,6 +114,32 @@ export function getOriginalLocation(rule: Rule, classSelector: ClassName): Locat
|
|
|
127
114
|
};
|
|
128
115
|
}
|
|
129
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Get the original location of `@value`.
|
|
119
|
+
* @param atValue The `@value` rule.
|
|
120
|
+
* @returns The location of the `@value` rule.
|
|
121
|
+
*/
|
|
122
|
+
export function getOriginalLocationOfAtValue(atValue: AtRule, valueDeclaration: ValueDeclaration): Location {
|
|
123
|
+
// The node derived from `postcss.parse` always has `source` property. Therefore, this line is unreachable.
|
|
124
|
+
if (atValue.source === undefined) throw new Error('Node#source is undefined');
|
|
125
|
+
// The node derived from `postcss.parse` always has `start` and `end` property. Therefore, this line is unreachable.
|
|
126
|
+
if (atValue.source.start === undefined) throw new Error('Node#start is undefined');
|
|
127
|
+
if (atValue.source.end === undefined) throw new Error('Node#end is undefined');
|
|
128
|
+
if (atValue.source.input.file === undefined) throw new Error('Node#input.file is undefined');
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
filePath: atValue.source.input.file,
|
|
132
|
+
start: {
|
|
133
|
+
line: atValue.source.start.line,
|
|
134
|
+
column: atValue.source.start.column + 7, // Add for `@value `
|
|
135
|
+
},
|
|
136
|
+
end: {
|
|
137
|
+
line: atValue.source.start.line,
|
|
138
|
+
column: atValue.source.start.column + 7 + valueDeclaration.tokenName.length, // Add for `@value ` and token name
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
130
143
|
function isAtRuleNode(node: Node): node is AtRule {
|
|
131
144
|
return node.type === 'atrule';
|
|
132
145
|
}
|
|
@@ -143,16 +156,9 @@ function isRuleNode(node: Node): node is Rule {
|
|
|
143
156
|
return node.type === 'rule';
|
|
144
157
|
}
|
|
145
158
|
|
|
146
|
-
function isDeclaration(node: Node): node is Declaration {
|
|
147
|
-
return node.type === 'decl';
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function isComposesDeclaration(node: Node): node is Declaration {
|
|
151
|
-
return isDeclaration(node) && node.prop === 'composes';
|
|
152
|
-
}
|
|
153
|
-
|
|
154
159
|
type CollectNodesResult = {
|
|
155
160
|
atImports: AtRule[];
|
|
161
|
+
atValues: AtRule[];
|
|
156
162
|
classSelectors: { rule: Rule; classSelector: ClassName }[];
|
|
157
163
|
};
|
|
158
164
|
|
|
@@ -162,10 +168,13 @@ type CollectNodesResult = {
|
|
|
162
168
|
*/
|
|
163
169
|
export function collectNodes(ast: Root): CollectNodesResult {
|
|
164
170
|
const atImports: AtRule[] = [];
|
|
171
|
+
const atValues: AtRule[] = [];
|
|
165
172
|
const classSelectors: { rule: Rule; classSelector: ClassName }[] = [];
|
|
166
173
|
ast.walk((node) => {
|
|
167
174
|
if (isAtImportNode(node)) {
|
|
168
175
|
atImports.push(node);
|
|
176
|
+
} else if (isAtValueNode(node)) {
|
|
177
|
+
atValues.push(node);
|
|
169
178
|
} else if (isRuleNode(node)) {
|
|
170
179
|
// In `rule.selector` comes the following string:
|
|
171
180
|
// 1. ".foo"
|
|
@@ -183,7 +192,7 @@ export function collectNodes(ast: Root): CollectNodesResult {
|
|
|
183
192
|
}).processSync(node);
|
|
184
193
|
}
|
|
185
194
|
});
|
|
186
|
-
return { atImports, classSelectors };
|
|
195
|
+
return { atImports, atValues, classSelectors };
|
|
187
196
|
}
|
|
188
197
|
|
|
189
198
|
/**
|
|
@@ -202,3 +211,78 @@ export function parseAtImport(atImport: AtRule): string | undefined {
|
|
|
202
211
|
}
|
|
203
212
|
return undefined;
|
|
204
213
|
}
|
|
214
|
+
|
|
215
|
+
type ValueDeclaration = {
|
|
216
|
+
type: 'valueDeclaration';
|
|
217
|
+
tokenName: string;
|
|
218
|
+
// value: string; // unneeded
|
|
219
|
+
};
|
|
220
|
+
type ValueImportDeclaration = {
|
|
221
|
+
type: 'valueImportDeclaration';
|
|
222
|
+
imports: { importedTokenName: string; localTokenName: string }[];
|
|
223
|
+
from: string;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
type ParsedAtValue = ValueDeclaration | ValueImportDeclaration;
|
|
227
|
+
|
|
228
|
+
const matchImports = /^(.+?|\([\s\S]+?\))\s+from\s+("[^"]*"|'[^']*'|[\w-]+)$/u;
|
|
229
|
+
const matchValueDefinition = /(?:\s+|^)([\w-]+):?(.*?)$/u;
|
|
230
|
+
const matchImport = /^([\w-]+)(?:\s+as\s+([\w-]+))?/u;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Parse the `@value` rule.
|
|
234
|
+
* Forked from https://github.com/css-modules/postcss-modules-values/blob/v4.0.0/src/index.js.
|
|
235
|
+
*
|
|
236
|
+
* @license
|
|
237
|
+
* ISC License (ISC)
|
|
238
|
+
* Copyright (c) 2015, Glen Maddern
|
|
239
|
+
*
|
|
240
|
+
* Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted,
|
|
241
|
+
* provided that the above copyright notice and this permission notice appear in all copies.
|
|
242
|
+
*
|
|
243
|
+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING
|
|
244
|
+
* ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
245
|
+
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
|
246
|
+
* WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH
|
|
247
|
+
* THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
248
|
+
*/
|
|
249
|
+
export function parseAtValue(atValue: AtRule): ParsedAtValue {
|
|
250
|
+
const matchesForImports = atValue.params.match(matchImports);
|
|
251
|
+
if (matchesForImports) {
|
|
252
|
+
const [, aliases, path] = matchesForImports;
|
|
253
|
+
|
|
254
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
255
|
+
if (aliases === undefined || path === undefined) throw new Error(`unreachable`);
|
|
256
|
+
|
|
257
|
+
const imports = aliases
|
|
258
|
+
.replace(/^\(\s*([\s\S]+)\s*\)$/u, '$1')
|
|
259
|
+
.split(/\s*,\s*/u)
|
|
260
|
+
.map((alias) => {
|
|
261
|
+
const tokens = matchImport.exec(alias);
|
|
262
|
+
|
|
263
|
+
if (tokens) {
|
|
264
|
+
const [, theirName, myName] = tokens;
|
|
265
|
+
if (theirName === undefined) throw new Error(`unreachable`);
|
|
266
|
+
return { importedTokenName: theirName, localTokenName: myName ?? theirName };
|
|
267
|
+
} else {
|
|
268
|
+
throw new Error(`@import statement "${alias}" is invalid!`);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Remove quotes from the path.
|
|
273
|
+
// NOTE: This is a restriction unique to "happy-css-modules" and not a specification of CSS Modules.
|
|
274
|
+
const normalizedPath = path.replace(/^['"]|['"]$/gu, '');
|
|
275
|
+
|
|
276
|
+
return { type: 'valueImportDeclaration', imports, from: normalizedPath };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const matchesForValueDefinitions = `${atValue.params}${atValue.raws.between!}`.match(matchValueDefinition);
|
|
280
|
+
if (matchesForValueDefinitions) {
|
|
281
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
282
|
+
const [, key, value] = matchesForValueDefinitions;
|
|
283
|
+
if (key === undefined) throw new Error(`unreachable`);
|
|
284
|
+
return { type: 'valueDeclaration', tokenName: key };
|
|
285
|
+
}
|
|
286
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
287
|
+
throw new Error(`@value statement "${atValue.source!}" is invalid!`);
|
|
288
|
+
}
|
package/src/test-util/util.ts
CHANGED
|
@@ -21,31 +21,36 @@ export function createAtImports(root: Root): AtRule[] {
|
|
|
21
21
|
return collectNodes(root).atImports;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export function createAtValues(root: Root): AtRule[] {
|
|
25
|
+
return collectNodes(root).atValues;
|
|
26
|
+
}
|
|
27
|
+
|
|
24
28
|
export function createClassSelectors(root: Root): { rule: Rule; classSelector: ClassName }[] {
|
|
25
29
|
return collectNodes(root).classSelectors;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
export function fakeToken(args: {
|
|
29
33
|
name: Token['name'];
|
|
30
|
-
|
|
34
|
+
originalLocation: { filePath?: Location['filePath']; start?: Location['start'] };
|
|
31
35
|
}): Token {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
if (args.originalLocation.filePath === undefined || args.originalLocation.start === undefined) {
|
|
37
|
+
return {
|
|
38
|
+
name: args.name,
|
|
39
|
+
originalLocation: { filePath: undefined, start: undefined, end: undefined },
|
|
40
|
+
};
|
|
41
|
+
} else {
|
|
42
|
+
return {
|
|
43
|
+
name: args.name,
|
|
44
|
+
originalLocation: {
|
|
45
|
+
filePath: args.originalLocation.filePath ?? getFixturePath('/test/1.css'),
|
|
46
|
+
start: args.originalLocation.start,
|
|
47
|
+
end: {
|
|
48
|
+
line: args.originalLocation.start.line,
|
|
49
|
+
column: args.originalLocation.start.column + args.name.length - 1,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
export async function waitForAsyncTask(ms?: number): Promise<void> {
|
|
@@ -42,33 +42,43 @@ test('handles less features', async () => {
|
|
|
42
42
|
tokens: [
|
|
43
43
|
{
|
|
44
44
|
name: "b_1",
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
originalLocation: {
|
|
46
|
+
filePath: "<fixtures>/test/2.less",
|
|
47
|
+
start: { line: 1, column: 1 },
|
|
48
|
+
end: { line: 1, column: 4 },
|
|
49
|
+
},
|
|
48
50
|
},
|
|
49
51
|
{
|
|
50
52
|
name: "a_1",
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
originalLocation: {
|
|
54
|
+
filePath: "<fixtures>/test/1.less",
|
|
55
|
+
start: { line: 2, column: 1 },
|
|
56
|
+
end: { line: 2, column: 4 },
|
|
57
|
+
},
|
|
54
58
|
},
|
|
55
59
|
{
|
|
56
60
|
name: "a_2",
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
originalLocation: {
|
|
62
|
+
filePath: "<fixtures>/test/1.less",
|
|
63
|
+
start: { line: 3, column: 1 },
|
|
64
|
+
end: { line: 3, column: 4 },
|
|
65
|
+
},
|
|
60
66
|
},
|
|
61
67
|
{
|
|
62
68
|
name: "a_2_1",
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
originalLocation: {
|
|
70
|
+
filePath: "<fixtures>/test/1.less",
|
|
71
|
+
start: { line: 6, column: 3 },
|
|
72
|
+
end: { line: 6, column: 8 },
|
|
73
|
+
},
|
|
66
74
|
},
|
|
67
75
|
{
|
|
68
76
|
name: "a_2_2",
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
originalLocation: {
|
|
78
|
+
filePath: "<fixtures>/test/1.less",
|
|
79
|
+
start: { line: 7, column: 3 },
|
|
80
|
+
end: { line: 7, column: 8 },
|
|
81
|
+
},
|
|
72
82
|
},
|
|
73
83
|
],
|
|
74
84
|
}
|
|
@@ -41,9 +41,11 @@ test('handles postcss features', async () => {
|
|
|
41
41
|
tokens: [
|
|
42
42
|
{
|
|
43
43
|
name: "foo_bar",
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
originalLocation: {
|
|
45
|
+
filePath: "<fixtures>/test/1.css",
|
|
46
|
+
start: { line: 2, column: 1 },
|
|
47
|
+
end: { line: 2, column: 8 },
|
|
48
|
+
},
|
|
47
49
|
},
|
|
48
50
|
],
|
|
49
51
|
}
|