happy-css-modules 3.0.0 → 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.
Files changed (143) hide show
  1. package/package.json +1 -1
  2. package/src/emitter/dts.test.ts +3 -3
  3. package/src/emitter/dts.ts +59 -37
  4. package/src/emitter/index.test.ts +3 -3
  5. package/src/integration-test/go-to-definition.test.ts +0 -35
  6. package/src/locator/index.test.ts +152 -158
  7. package/src/locator/index.ts +44 -37
  8. package/src/locator/postcss.test.ts +118 -76
  9. package/src/locator/postcss.ts +116 -73
  10. package/src/test-util/util.ts +24 -23
  11. package/src/transformer/less-transformer.test.ts +26 -24
  12. package/src/transformer/postcss-transformer.test.ts +5 -3
  13. package/src/transformer/scss-transformer.test.ts +39 -28
  14. package/src/util.ts +1 -1
  15. package/dist/cli.d.ts +0 -6
  16. package/dist/cli.js +0 -121
  17. package/dist/cli.js.map +0 -1
  18. package/dist/cli.test.d.ts +0 -1
  19. package/dist/cli.test.js +0 -66
  20. package/dist/cli.test.js.map +0 -1
  21. package/dist/config.d.ts +0 -1
  22. package/dist/config.js +0 -2
  23. package/dist/config.js.map +0 -1
  24. package/dist/emitter/dts.d.ts +0 -14
  25. package/dist/emitter/dts.js +0 -111
  26. package/dist/emitter/dts.js.map +0 -1
  27. package/dist/emitter/dts.test.d.ts +0 -1
  28. package/dist/emitter/dts.test.js +0 -205
  29. package/dist/emitter/dts.test.js.map +0 -1
  30. package/dist/emitter/file-system.d.ts +0 -6
  31. package/dist/emitter/file-system.js +0 -26
  32. package/dist/emitter/file-system.js.map +0 -1
  33. package/dist/emitter/file-system.test.d.ts +0 -1
  34. package/dist/emitter/file-system.test.js +0 -34
  35. package/dist/emitter/file-system.test.js.map +0 -1
  36. package/dist/emitter/index.d.ts +0 -34
  37. package/dist/emitter/index.js +0 -49
  38. package/dist/emitter/index.js.map +0 -1
  39. package/dist/emitter/index.test.d.ts +0 -1
  40. package/dist/emitter/index.test.js +0 -68
  41. package/dist/emitter/index.test.js.map +0 -1
  42. package/dist/emitter/source-map.d.ts +0 -8
  43. package/dist/emitter/source-map.js +0 -16
  44. package/dist/emitter/source-map.js.map +0 -1
  45. package/dist/emitter/source-map.test.d.ts +0 -1
  46. package/dist/emitter/source-map.test.js +0 -13
  47. package/dist/emitter/source-map.test.js.map +0 -1
  48. package/dist/index.d.ts +0 -5
  49. package/dist/index.js +0 -6
  50. package/dist/index.js.map +0 -1
  51. package/dist/integration-test/go-to-definition.test.d.ts +0 -1
  52. package/dist/integration-test/go-to-definition.test.js +0 -369
  53. package/dist/integration-test/go-to-definition.test.js.map +0 -1
  54. package/dist/library/source-map/index.d.ts +0 -8
  55. package/dist/library/source-map/index.js +0 -5
  56. package/dist/library/source-map/index.js.map +0 -1
  57. package/dist/locator/index.d.ts +0 -44
  58. package/dist/locator/index.js +0 -162
  59. package/dist/locator/index.js.map +0 -1
  60. package/dist/locator/index.test.d.ts +0 -1
  61. package/dist/locator/index.test.js +0 -395
  62. package/dist/locator/index.test.js.map +0 -1
  63. package/dist/locator/postcss.d.ts +0 -64
  64. package/dist/locator/postcss.js +0 -208
  65. package/dist/locator/postcss.js.map +0 -1
  66. package/dist/locator/postcss.test.d.ts +0 -1
  67. package/dist/locator/postcss.test.js +0 -242
  68. package/dist/locator/postcss.test.js.map +0 -1
  69. package/dist/logger.d.ts +0 -9
  70. package/dist/logger.js +0 -28
  71. package/dist/logger.js.map +0 -1
  72. package/dist/regression-test/issue-168.test.d.ts +0 -1
  73. package/dist/regression-test/issue-168.test.js +0 -30
  74. package/dist/regression-test/issue-168.test.js.map +0 -1
  75. package/dist/resolver/index.d.ts +0 -22
  76. package/dist/resolver/index.js +0 -44
  77. package/dist/resolver/index.js.map +0 -1
  78. package/dist/resolver/index.test.d.ts +0 -1
  79. package/dist/resolver/index.test.js +0 -16
  80. package/dist/resolver/index.test.js.map +0 -1
  81. package/dist/resolver/node-resolver.d.ts +0 -2
  82. package/dist/resolver/node-resolver.js +0 -6
  83. package/dist/resolver/node-resolver.js.map +0 -1
  84. package/dist/resolver/node-resolver.test.d.ts +0 -1
  85. package/dist/resolver/node-resolver.test.js +0 -25
  86. package/dist/resolver/node-resolver.test.js.map +0 -1
  87. package/dist/resolver/relative-resolver.d.ts +0 -2
  88. package/dist/resolver/relative-resolver.js +0 -5
  89. package/dist/resolver/relative-resolver.js.map +0 -1
  90. package/dist/resolver/relative-resolver.test.d.ts +0 -1
  91. package/dist/resolver/relative-resolver.test.js +0 -12
  92. package/dist/resolver/relative-resolver.test.js.map +0 -1
  93. package/dist/resolver/webpack-resolver.d.ts +0 -24
  94. package/dist/resolver/webpack-resolver.js +0 -91
  95. package/dist/resolver/webpack-resolver.js.map +0 -1
  96. package/dist/resolver/webpack-resolver.test.d.ts +0 -1
  97. package/dist/resolver/webpack-resolver.test.js +0 -89
  98. package/dist/resolver/webpack-resolver.test.js.map +0 -1
  99. package/dist/runner.d.ts +0 -76
  100. package/dist/runner.js +0 -120
  101. package/dist/runner.js.map +0 -1
  102. package/dist/runner.test.d.ts +0 -1
  103. package/dist/runner.test.js +0 -287
  104. package/dist/runner.test.js.map +0 -1
  105. package/dist/test-util/jest/resolver.cjs +0 -31
  106. package/dist/test-util/jest/resolver.cjs.map +0 -1
  107. package/dist/test-util/jest/resolver.d.cts +0 -16
  108. package/dist/test-util/tsserver.d.ts +0 -31
  109. package/dist/test-util/tsserver.js +0 -112
  110. package/dist/test-util/tsserver.js.map +0 -1
  111. package/dist/test-util/util.d.ts +0 -29
  112. package/dist/test-util/util.js +0 -84
  113. package/dist/test-util/util.js.map +0 -1
  114. package/dist/transformer/index.d.ts +0 -30
  115. package/dist/transformer/index.js +0 -25
  116. package/dist/transformer/index.js.map +0 -1
  117. package/dist/transformer/index.test.d.ts +0 -1
  118. package/dist/transformer/index.test.js +0 -66
  119. package/dist/transformer/index.test.js.map +0 -1
  120. package/dist/transformer/less-transformer.d.ts +0 -2
  121. package/dist/transformer/less-transformer.js +0 -45
  122. package/dist/transformer/less-transformer.js.map +0 -1
  123. package/dist/transformer/less-transformer.test.d.ts +0 -1
  124. package/dist/transformer/less-transformer.test.js +0 -132
  125. package/dist/transformer/less-transformer.test.js.map +0 -1
  126. package/dist/transformer/postcss-transformer.d.ts +0 -12
  127. package/dist/transformer/postcss-transformer.js +0 -32
  128. package/dist/transformer/postcss-transformer.js.map +0 -1
  129. package/dist/transformer/postcss-transformer.test.d.ts +0 -1
  130. package/dist/transformer/postcss-transformer.test.js +0 -176
  131. package/dist/transformer/postcss-transformer.test.js.map +0 -1
  132. package/dist/transformer/scss-transformer.d.ts +0 -2
  133. package/dist/transformer/scss-transformer.js +0 -40
  134. package/dist/transformer/scss-transformer.js.map +0 -1
  135. package/dist/transformer/scss-transformer.test.d.ts +0 -1
  136. package/dist/transformer/scss-transformer.test.js +0 -138
  137. package/dist/transformer/scss-transformer.test.js.map +0 -1
  138. package/dist/util.d.ts +0 -21
  139. package/dist/util.js +0 -72
  140. package/dist/util.js.map +0 -1
  141. package/dist/util.test.d.ts +0 -1
  142. package/dist/util.test.js +0 -74
  143. package/dist/util.test.js.map +0 -1
@@ -1,29 +1,13 @@
1
- import fs, { readFile, writeFile } from 'fs/promises';
1
+ import { readFile, writeFile } from 'fs/promises';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { jest } from '@jest/globals';
4
4
  import dedent from 'dedent';
5
- import { createDefaultTransformer } from '../index.js';
6
- import { createFixtures, FIXTURE_DIR_PATH, getFixturePath } from '../test-util/util.js';
5
+ import { Locator, createDefaultTransformer } from '../index.js';
6
+ import { createFixtures, getFixturePath } from '../test-util/util.js';
7
7
  import { sleepSync } from '../util.js';
8
8
 
9
- const readFileSpy = jest.spyOn(fs, 'readFile');
10
- // In ESM, for some reason, we need to explicitly mock module
11
- jest.unstable_mockModule('fs/promises', () => ({
12
- ...fs, // Inherit native functions (e.g., fs.stat)
13
- readFile: readFileSpy,
14
- }));
15
-
16
- // After the mock of fs/promises is complete, . /index.js after the mock of fs/promises is complete.
17
- // ref: https://www.coolcomputerclub.com/posts/jest-hoist-await/
18
- const { Locator } = await import('./index.js');
19
- // NOTE: ../test/util.js depends on . /index.js, so it must also be imported dynamically...
20
-
21
9
  const locator = new Locator();
22
10
 
23
- afterEach(() => {
24
- readFileSpy.mockClear();
25
- });
26
-
27
11
  test('basic', async () => {
28
12
  createFixtures({
29
13
  '/test/1.css': dedent`
@@ -38,15 +22,19 @@ test('basic', async () => {
38
22
  tokens: [
39
23
  {
40
24
  name: "a",
41
- originalLocations: [
42
- { filePath: "<fixtures>/test/1.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
43
- ],
25
+ originalLocation: {
26
+ filePath: "<fixtures>/test/1.css",
27
+ start: { line: 1, column: 1 },
28
+ end: { line: 1, column: 2 },
29
+ },
44
30
  },
45
31
  {
46
32
  name: "b",
47
- originalLocations: [
48
- { filePath: "<fixtures>/test/1.css", start: { line: 2, column: 1 }, end: { line: 2, column: 2 } },
49
- ],
33
+ originalLocation: {
34
+ filePath: "<fixtures>/test/1.css",
35
+ start: { line: 2, column: 1 },
36
+ end: { line: 2, column: 2 },
37
+ },
50
38
  },
51
39
  ],
52
40
  }
@@ -90,51 +78,86 @@ test('tracks other files when `@import` is present', async () => {
90
78
  tokens: [
91
79
  {
92
80
  name: "a",
93
- originalLocations: [
94
- { filePath: "<fixtures>/test/2.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
95
- ],
81
+ originalLocation: {
82
+ filePath: "<fixtures>/test/2.css",
83
+ start: { line: 1, column: 1 },
84
+ end: { line: 1, column: 2 },
85
+ },
96
86
  },
97
87
  {
98
88
  name: "b",
99
- originalLocations: [
100
- { filePath: "<fixtures>/test/3.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
101
- ],
89
+ originalLocation: {
90
+ filePath: "<fixtures>/test/3.css",
91
+ start: { line: 1, column: 1 },
92
+ end: { line: 1, column: 2 },
93
+ },
102
94
  },
103
95
  {
104
96
  name: "c",
105
- originalLocations: [
106
- { filePath: "<fixtures>/test/4.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
107
- ],
97
+ originalLocation: {
98
+ filePath: "<fixtures>/test/4.css",
99
+ start: { line: 1, column: 1 },
100
+ end: { line: 1, column: 2 },
101
+ },
108
102
  },
109
103
  {
110
104
  name: "d",
111
- originalLocations: [
112
- { filePath: "<fixtures>/test/5-recursive.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
113
- ],
105
+ originalLocation: {
106
+ filePath: "<fixtures>/test/5-recursive.css",
107
+ start: { line: 1, column: 1 },
108
+ end: { line: 1, column: 2 },
109
+ },
114
110
  },
115
111
  ],
116
112
  }
117
113
  `);
118
114
  });
119
115
 
120
- test('tracks other files when `composes` is present', async () => {
116
+ test('does not track other files by `composes`', async () => {
121
117
  createFixtures({
122
118
  '/test/1.css': dedent`
123
119
  .a {
124
120
  composes: b from './2.css';
125
- composes: c d from './3.css';
126
- composes: e from '${getFixturePath('/test/4.css')}';
121
+ composes: c from './3.css'; /* non-existent file */
127
122
  }
128
123
  `,
129
124
  '/test/2.css': dedent`
130
125
  .b {}
131
126
  `,
127
+ });
128
+ const result = await locator.load(getFixturePath('/test/1.css'));
129
+ expect(result).toMatchInlineSnapshot(`
130
+ {
131
+ dependencies: [],
132
+ tokens: [
133
+ {
134
+ name: "a",
135
+ originalLocation: {
136
+ filePath: "<fixtures>/test/1.css",
137
+ start: { line: 1, column: 1 },
138
+ end: { line: 1, column: 2 },
139
+ },
140
+ },
141
+ ],
142
+ }
143
+ `);
144
+ });
145
+
146
+ test('tracks other files when `@value` is present', async () => {
147
+ createFixtures({
148
+ '/test/1.css': dedent`
149
+ @value a from './2.css';
150
+ @value b from '3.css';
151
+ @value c from '${getFixturePath('/test/4.css')}';
152
+ `,
153
+ '/test/2.css': dedent`
154
+ @value a: 1;
155
+ `,
132
156
  '/test/3.css': dedent`
133
- .c {}
134
- .d {}
157
+ @value b: 2;
135
158
  `,
136
159
  '/test/4.css': dedent`
137
- .e {}
160
+ @value c: 3;
138
161
  `,
139
162
  });
140
163
  const result = await locator.load(getFixturePath('/test/1.css'));
@@ -144,103 +167,94 @@ test('tracks other files when `composes` is present', async () => {
144
167
  tokens: [
145
168
  {
146
169
  name: "a",
147
- originalLocations: [
148
- { filePath: "<fixtures>/test/1.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
149
- ],
170
+ originalLocation: {
171
+ filePath: "<fixtures>/test/2.css",
172
+ start: { line: 1, column: 8 },
173
+ end: { line: 1, column: 9 },
174
+ },
150
175
  },
151
176
  {
152
177
  name: "b",
153
- originalLocations: [
154
- { filePath: "<fixtures>/test/2.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
155
- ],
178
+ originalLocation: {
179
+ filePath: "<fixtures>/test/3.css",
180
+ start: { line: 1, column: 8 },
181
+ end: { line: 1, column: 9 },
182
+ },
156
183
  },
157
184
  {
158
185
  name: "c",
159
- originalLocations: [
160
- { filePath: "<fixtures>/test/3.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
161
- ],
162
- },
163
- {
164
- name: "d",
165
- originalLocations: [
166
- { filePath: "<fixtures>/test/3.css", start: { line: 2, column: 1 }, end: { line: 2, column: 2 } },
167
- ],
168
- },
169
- {
170
- name: "e",
171
- originalLocations: [
172
- { filePath: "<fixtures>/test/4.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
173
- ],
186
+ originalLocation: {
187
+ filePath: "<fixtures>/test/4.css",
188
+ start: { line: 1, column: 8 },
189
+ end: { line: 1, column: 9 },
190
+ },
174
191
  },
175
192
  ],
176
193
  }
177
194
  `);
178
195
  });
179
196
 
180
- test('normalizes tokens', async () => {
197
+ test('unique tokens', async () => {
181
198
  createFixtures({
182
199
  '/test/1.css': dedent`
183
200
  /* duplicate import */
184
201
  @import './2.css';
185
202
  @import '2.css';
186
- .a {
187
- /* duplicate composes */
188
- composes: c from './3.css';
189
- composes: c from '3.css';
190
- composes: c c from './3.css';
191
- /* duplicate import and composes */
192
- composes: b from './2.css';
193
- }
203
+ .a {}
194
204
  .a {} /* duplicate class selector */
195
205
  `,
196
206
  '/test/2.css': dedent`
197
207
  .a {} /* class selector that duplicates the import source */
198
208
  .b {}
199
209
  `,
200
- '/test/3.css': dedent`
201
- .c {}
202
- `,
203
210
  });
204
211
  const result = await locator.load(getFixturePath('/test/1.css'));
205
212
  expect(result).toMatchInlineSnapshot(`
206
213
  {
207
- dependencies: ["<fixtures>/test/2.css", "<fixtures>/test/3.css"],
214
+ dependencies: ["<fixtures>/test/2.css"],
208
215
  tokens: [
209
216
  {
210
217
  name: "a",
211
- originalLocations: [
212
- { filePath: "<fixtures>/test/2.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
213
- { filePath: "<fixtures>/test/1.css", start: { line: 4, column: 1 }, end: { line: 4, column: 2 } },
214
- { filePath: "<fixtures>/test/1.css", start: { line: 12, column: 1 }, end: { line: 12, column: 2 } },
215
- ],
218
+ originalLocation: {
219
+ filePath: "<fixtures>/test/2.css",
220
+ start: { line: 1, column: 1 },
221
+ end: { line: 1, column: 2 },
222
+ },
216
223
  },
217
224
  {
218
225
  name: "b",
219
- originalLocations: [
220
- { filePath: "<fixtures>/test/2.css", start: { line: 2, column: 1 }, end: { line: 2, column: 2 } },
221
- ],
226
+ originalLocation: {
227
+ filePath: "<fixtures>/test/2.css",
228
+ start: { line: 2, column: 1 },
229
+ end: { line: 2, column: 2 },
230
+ },
222
231
  },
223
232
  {
224
- name: "c",
225
- originalLocations: [
226
- { filePath: "<fixtures>/test/3.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
227
- ],
233
+ name: "a",
234
+ originalLocation: {
235
+ filePath: "<fixtures>/test/1.css",
236
+ start: { line: 4, column: 1 },
237
+ end: { line: 4, column: 2 },
238
+ },
239
+ },
240
+ {
241
+ name: "a",
242
+ originalLocation: {
243
+ filePath: "<fixtures>/test/1.css",
244
+ start: { line: 5, column: 1 },
245
+ end: { line: 5, column: 2 },
246
+ },
228
247
  },
229
248
  ],
230
249
  }
231
250
  `);
232
251
  });
233
252
 
234
- test.failing('returns the result from the cache when the file has not been modified', async () => {
253
+ test('returns the result from the cache when the file has not been modified', async () => {
235
254
  createFixtures({
236
255
  '/test/1.css': dedent`
237
256
  @import './2.css';
238
- @import './2.css';
239
- .a {
240
- composes: b from './2.css';
241
- composes: c from './3.css';
242
- composes: d from './3.css';
243
- }
257
+ @import './3.css';
244
258
  `,
245
259
  '/test/2.css': dedent`
246
260
  .b {}
@@ -250,12 +264,14 @@ test.failing('returns the result from the cache when the file has not been modif
250
264
  .d {}
251
265
  `,
252
266
  });
267
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
268
+ const readCSSSpy = jest.spyOn(locator, 'readCSS' as any);
253
269
  await locator.load(getFixturePath('/test/1.css'));
254
- expect(readFileSpy).toHaveBeenCalledTimes(3);
255
- expect(readFileSpy).toHaveBeenNthCalledWith(1, '/test/1.css', 'utf-8');
256
- expect(readFileSpy).toHaveBeenNthCalledWith(2, '/test/2.css', 'utf-8');
257
- expect(readFileSpy).toHaveBeenNthCalledWith(3, '/test/3.css', 'utf-8');
258
- readFileSpy.mockClear();
270
+ expect(readCSSSpy).toHaveBeenCalledTimes(3);
271
+ expect(readCSSSpy).toHaveBeenNthCalledWith(1, getFixturePath('/test/1.css'));
272
+ expect(readCSSSpy).toHaveBeenNthCalledWith(2, getFixturePath('/test/2.css'));
273
+ expect(readCSSSpy).toHaveBeenNthCalledWith(3, getFixturePath('/test/3.css'));
274
+ readCSSSpy.mockClear();
259
275
 
260
276
  // update `/test/2.css`
261
277
  sleepSync(1); // wait for the file system to update the mtime
@@ -263,54 +279,17 @@ test.failing('returns the result from the cache when the file has not been modif
263
279
 
264
280
  // `3.css` is not updated, so the cache is used. Therefore, `readFile` is not called.
265
281
  await locator.load(getFixturePath('/test/3.css'));
266
- expect(readFileSpy).toHaveBeenCalledTimes(0);
282
+ expect(readCSSSpy).toHaveBeenCalledTimes(0);
267
283
 
268
284
  // `1.css` is not updated, but dependencies are updated, so the cache is used. Therefore, `readFile` is called.
269
285
  await locator.load(getFixturePath('/test/1.css'));
270
- expect(readFileSpy).toHaveBeenCalledTimes(2);
271
- expect(readFileSpy).toHaveBeenNthCalledWith(1, '/test/1.css', 'utf-8');
272
- expect(readFileSpy).toHaveBeenNthCalledWith(2, '/test/2.css', 'utf-8');
286
+ expect(readCSSSpy).toHaveBeenCalledTimes(2);
287
+ expect(readCSSSpy).toHaveBeenNthCalledWith(1, getFixturePath('/test/1.css'));
288
+ expect(readCSSSpy).toHaveBeenNthCalledWith(2, getFixturePath('/test/2.css'));
273
289
 
274
290
  // ``2.css` is updated, but the cache is already available because it was updated in the previous step. Therefore, `readFile` is not called.
275
291
  await locator.load(getFixturePath('/test/2.css'));
276
- expect(readFileSpy).toHaveBeenCalledTimes(2);
277
- });
278
-
279
- test('ignores the composition of non-existent tokens', async () => {
280
- // In css-loader and postcss-modules, compositions of non-existent tokens are simply ignored.
281
- // Therefore, happy-css-modules follows suit.
282
- // It may be preferable to warn rather than ignore, but for now, we will focus on compatibility.
283
- // ref: https://github.com/css-modules/css-modules/issues/356
284
- createFixtures({
285
- '/test/1.css': dedent`
286
- .a {
287
- composes: b c from './2.css';
288
- }
289
- `,
290
- '/test/2.css': dedent`
291
- .b {}
292
- `,
293
- });
294
- const result = await locator.load(getFixturePath('/test/1.css'));
295
- expect(result.tokens.map((t) => t.name)).toStrictEqual(['a', 'b']);
296
- });
297
-
298
- test('throws error the composition of non-existent file', async () => {
299
- // In postcss-modules, compositions of non-existent file are causes an error.
300
- // Therefore, happy-css-modules follows suit.
301
- createFixtures({
302
- '/test/1.css': dedent`
303
- .a {
304
- composes: a from './2.css';
305
- }
306
- `,
307
- });
308
- await expect(async () => {
309
- await locator.load(getFixturePath('/test/1.css')).catch((e) => {
310
- e.message = e.message.replace(FIXTURE_DIR_PATH, '<fixtures>');
311
- throw e;
312
- });
313
- }).rejects.toThrowError(`Could not resolve './2.css' in '<fixtures>/test/1.css'`);
292
+ expect(readCSSSpy).toHaveBeenCalledTimes(2);
314
293
  });
315
294
 
316
295
  describe('supports sourcemap', () => {
@@ -334,16 +313,27 @@ describe('supports sourcemap', () => {
334
313
  tokens: [
335
314
  {
336
315
  name: "nesting",
337
- originalLocations: [
338
- { filePath: "<fixtures>/test/1.scss", start: { line: 1, column: 1 }, end: { line: 1, column: 8 } },
339
- { filePath: "<fixtures>/test/1.scss", start: { line: 3, column: 3 }, end: { line: 3, column: 10 } },
340
- ],
316
+ originalLocation: {
317
+ filePath: "<fixtures>/test/1.scss",
318
+ start: { line: 1, column: 1 },
319
+ end: { line: 1, column: 8 },
320
+ },
321
+ },
322
+ {
323
+ name: "nesting",
324
+ originalLocation: {
325
+ filePath: "<fixtures>/test/1.scss",
326
+ start: { line: 3, column: 3 },
327
+ end: { line: 3, column: 10 },
328
+ },
341
329
  },
342
330
  {
343
331
  name: "nesting_child",
344
- originalLocations: [
345
- { filePath: "<fixtures>/test/1.scss", start: { line: 3, column: 3 }, end: { line: 3, column: 16 } },
346
- ],
332
+ originalLocation: {
333
+ filePath: "<fixtures>/test/1.scss",
334
+ start: { line: 3, column: 3 },
335
+ end: { line: 3, column: 16 },
336
+ },
347
337
  },
348
338
  ],
349
339
  }
@@ -373,18 +363,22 @@ describe('supports sourcemap', () => {
373
363
  tokens: [
374
364
  {
375
365
  name: "selector_list_a_1",
376
- originalLocations: [
377
- { filePath: "<fixtures>/test/1.css", start: { line: 1, column: 1 }, end: { line: 1, column: 18 } },
378
- ],
366
+ originalLocation: {
367
+ filePath: "<fixtures>/test/1.css",
368
+ start: { line: 1, column: 1 },
369
+ end: { line: 1, column: 18 },
370
+ },
379
371
  },
380
372
  {
381
373
  name: "selector_list_a_2",
382
- originalLocations: [
383
- { filePath: "<fixtures>/test/1.css", start: { line: 1, column: 1 }, end: { line: 1, column: 18 } },
384
- ],
374
+ originalLocation: {
375
+ filePath: "<fixtures>/test/1.css",
376
+ start: { line: 1, column: 1 },
377
+ end: { line: 1, column: 18 },
378
+ },
385
379
  },
386
- { name: "selector_list_b_1", originalLocations: [{}] },
387
- { name: "selector_list_b_2", originalLocations: [{}] },
380
+ { name: "selector_list_b_1", originalLocation: {} },
381
+ { name: "selector_list_b_2", originalLocation: {} },
388
382
  ],
389
383
  }
390
384
  `);
@@ -5,12 +5,13 @@ import { createDefaultResolver } from '../resolver/index.js';
5
5
  import { createDefaultTransformer, type Transformer } from '../transformer/index.js';
6
6
  import { unique, uniqueBy } from '../util.js';
7
7
  import {
8
- getOriginalLocation,
8
+ getOriginalLocationOfClassSelector,
9
+ getOriginalLocationOfAtValue,
9
10
  generateLocalTokenNames,
10
11
  parseAtImport,
11
12
  type Location,
12
- parseComposesDeclarationWithFromUrl,
13
13
  collectNodes,
14
+ parseAtValue,
14
15
  } from './postcss.js';
15
16
 
16
17
  export { collectNodes, type Location } from './postcss.js';
@@ -27,8 +28,10 @@ function isIgnoredSpecifier(specifier: string): boolean {
27
28
  export type Token = {
28
29
  /** The token name. */
29
30
  name: string;
30
- /** The original locations of the token in the source file. */
31
- originalLocations: Location[];
31
+ /** The name of the imported token. */
32
+ importedName?: string;
33
+ /** The original location of the token in the source file. */
34
+ originalLocation: Location;
32
35
  };
33
36
 
34
37
  type CacheEntry = {
@@ -38,28 +41,12 @@ type CacheEntry = {
38
41
 
39
42
  /** The result of `Locator#load`. */
40
43
  export type LoadResult = {
41
- /** The path of the file imported from the source file with `@import` or `composes`. */
44
+ /** The path of the file imported from the source file with `@import`. */
42
45
  dependencies: string[];
43
46
  /** The tokens exported by the source file. */
44
47
  tokens: Token[];
45
48
  };
46
49
 
47
- function normalizeTokens(tokens: Token[]): Token[] {
48
- const tokenNameToOriginalLocations = new Map<string, Location[]>();
49
- for (const token of tokens) {
50
- tokenNameToOriginalLocations.set(
51
- token.name,
52
- uniqueBy([...(tokenNameToOriginalLocations.get(token.name) ?? []), ...token.originalLocations], (location) =>
53
- JSON.stringify(location),
54
- ),
55
- );
56
- }
57
- return Array.from(tokenNameToOriginalLocations.entries()).map(([name, originalLocations]) => ({
58
- name,
59
- originalLocations,
60
- }));
61
- }
62
-
63
50
  export type LocatorOptions = {
64
51
  /** The function to transform source code. */
65
52
  transformer?: Transformer | undefined;
@@ -165,7 +152,7 @@ export class Locator {
165
152
 
166
153
  const tokens: Token[] = [];
167
154
 
168
- const { atImports, classSelectors, composesDeclarations } = collectNodes(ast);
155
+ const { atImports, atValues, classSelectors } = collectNodes(ast);
169
156
 
170
157
  // Load imported sheets recursively.
171
158
  for (const atImport of atImports) {
@@ -187,31 +174,51 @@ export class Locator {
187
174
  // NOTE: This method has false positives. However, it works as expected in many cases.
188
175
  if (!localTokenNames.includes(classSelector.value)) continue;
189
176
 
190
- const originalLocation = getOriginalLocation(rule, classSelector);
177
+ const originalLocation = getOriginalLocationOfClassSelector(rule, classSelector);
191
178
 
192
179
  tokens.push({
193
180
  name: classSelector.value,
194
- originalLocations: [originalLocation],
181
+ originalLocation,
195
182
  });
196
183
  }
197
184
 
198
- // Load imported tokens by the names recursively.
199
- for (const composesDeclaration of composesDeclarations) {
200
- const declarationDetail = parseComposesDeclarationWithFromUrl(composesDeclaration);
201
- if (!declarationDetail) continue;
202
- if (isIgnoredSpecifier(declarationDetail.from)) continue;
203
- // eslint-disable-next-line no-await-in-loop
204
- const from = await this.resolver(declarationDetail.from, { request: filePath });
205
- // eslint-disable-next-line no-await-in-loop
206
- const result = await this._load(from);
207
- const externalTokens = result.tokens.filter((token) => declarationDetail.tokenNames.includes(token.name));
208
- dependencies.push(from, ...result.dependencies);
209
- tokens.push(...externalTokens);
185
+ for (const atValue of atValues) {
186
+ const parsedAtValue = parseAtValue(atValue);
187
+
188
+ if (parsedAtValue.type === 'valueDeclaration') {
189
+ tokens.push({
190
+ name: parsedAtValue.tokenName,
191
+ originalLocation: getOriginalLocationOfAtValue(atValue, parsedAtValue),
192
+ });
193
+ } else if (parsedAtValue.type === 'valueImportDeclaration') {
194
+ if (isIgnoredSpecifier(parsedAtValue.from)) continue;
195
+ // eslint-disable-next-line no-await-in-loop
196
+ const from = await this.resolver(parsedAtValue.from, { request: filePath });
197
+ // eslint-disable-next-line no-await-in-loop
198
+ const result = await this._load(from);
199
+ dependencies.push(from, ...result.dependencies);
200
+ for (const token of result.tokens) {
201
+ const matchedImport = parsedAtValue.imports.find((i) => i.importedTokenName === token.name);
202
+ if (!matchedImport) continue;
203
+ if (matchedImport.localTokenName === matchedImport.importedTokenName) {
204
+ tokens.push({
205
+ name: matchedImport.localTokenName,
206
+ originalLocation: token.originalLocation,
207
+ });
208
+ } else {
209
+ tokens.push({
210
+ name: matchedImport.localTokenName,
211
+ importedName: matchedImport.importedTokenName,
212
+ originalLocation: token.originalLocation,
213
+ });
214
+ }
215
+ }
216
+ }
210
217
  }
211
218
 
212
219
  const result: LoadResult = {
213
220
  dependencies: unique(dependencies).filter((dep) => dep !== filePath),
214
- tokens: normalizeTokens(tokens),
221
+ tokens: uniqueBy(tokens, (token) => JSON.stringify(token)),
215
222
  };
216
223
  this.cache.set(filePath, { mtime, result });
217
224
  return result;