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.
Files changed (141) 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/locator/index.test.ts +143 -48
  6. package/src/locator/index.ts +51 -23
  7. package/src/locator/postcss.test.ts +135 -26
  8. package/src/locator/postcss.ts +120 -36
  9. package/src/test-util/util.ts +23 -18
  10. package/src/transformer/less-transformer.test.ts +25 -15
  11. package/src/transformer/postcss-transformer.test.ts +5 -3
  12. package/src/transformer/scss-transformer.test.ts +38 -19
  13. package/dist/cli.d.ts +0 -6
  14. package/dist/cli.js +0 -121
  15. package/dist/cli.js.map +0 -1
  16. package/dist/cli.test.d.ts +0 -1
  17. package/dist/cli.test.js +0 -66
  18. package/dist/cli.test.js.map +0 -1
  19. package/dist/config.d.ts +0 -1
  20. package/dist/config.js +0 -2
  21. package/dist/config.js.map +0 -1
  22. package/dist/emitter/dts.d.ts +0 -14
  23. package/dist/emitter/dts.js +0 -111
  24. package/dist/emitter/dts.js.map +0 -1
  25. package/dist/emitter/dts.test.d.ts +0 -1
  26. package/dist/emitter/dts.test.js +0 -205
  27. package/dist/emitter/dts.test.js.map +0 -1
  28. package/dist/emitter/file-system.d.ts +0 -6
  29. package/dist/emitter/file-system.js +0 -26
  30. package/dist/emitter/file-system.js.map +0 -1
  31. package/dist/emitter/file-system.test.d.ts +0 -1
  32. package/dist/emitter/file-system.test.js +0 -34
  33. package/dist/emitter/file-system.test.js.map +0 -1
  34. package/dist/emitter/index.d.ts +0 -34
  35. package/dist/emitter/index.js +0 -49
  36. package/dist/emitter/index.js.map +0 -1
  37. package/dist/emitter/index.test.d.ts +0 -1
  38. package/dist/emitter/index.test.js +0 -68
  39. package/dist/emitter/index.test.js.map +0 -1
  40. package/dist/emitter/source-map.d.ts +0 -8
  41. package/dist/emitter/source-map.js +0 -16
  42. package/dist/emitter/source-map.js.map +0 -1
  43. package/dist/emitter/source-map.test.d.ts +0 -1
  44. package/dist/emitter/source-map.test.js +0 -13
  45. package/dist/emitter/source-map.test.js.map +0 -1
  46. package/dist/index.d.ts +0 -5
  47. package/dist/index.js +0 -6
  48. package/dist/index.js.map +0 -1
  49. package/dist/integration-test/go-to-definition.test.d.ts +0 -1
  50. package/dist/integration-test/go-to-definition.test.js +0 -334
  51. package/dist/integration-test/go-to-definition.test.js.map +0 -1
  52. package/dist/library/source-map/index.d.ts +0 -8
  53. package/dist/library/source-map/index.js +0 -5
  54. package/dist/library/source-map/index.js.map +0 -1
  55. package/dist/locator/index.d.ts +0 -44
  56. package/dist/locator/index.js +0 -147
  57. package/dist/locator/index.js.map +0 -1
  58. package/dist/locator/index.test.d.ts +0 -1
  59. package/dist/locator/index.test.js +0 -299
  60. package/dist/locator/index.test.js.map +0 -1
  61. package/dist/locator/postcss.d.ts +0 -53
  62. package/dist/locator/postcss.js +0 -169
  63. package/dist/locator/postcss.js.map +0 -1
  64. package/dist/locator/postcss.test.d.ts +0 -1
  65. package/dist/locator/postcss.test.js +0 -196
  66. package/dist/locator/postcss.test.js.map +0 -1
  67. package/dist/logger.d.ts +0 -9
  68. package/dist/logger.js +0 -28
  69. package/dist/logger.js.map +0 -1
  70. package/dist/regression-test/issue-168.test.d.ts +0 -1
  71. package/dist/regression-test/issue-168.test.js +0 -30
  72. package/dist/regression-test/issue-168.test.js.map +0 -1
  73. package/dist/resolver/index.d.ts +0 -22
  74. package/dist/resolver/index.js +0 -44
  75. package/dist/resolver/index.js.map +0 -1
  76. package/dist/resolver/index.test.d.ts +0 -1
  77. package/dist/resolver/index.test.js +0 -16
  78. package/dist/resolver/index.test.js.map +0 -1
  79. package/dist/resolver/node-resolver.d.ts +0 -2
  80. package/dist/resolver/node-resolver.js +0 -6
  81. package/dist/resolver/node-resolver.js.map +0 -1
  82. package/dist/resolver/node-resolver.test.d.ts +0 -1
  83. package/dist/resolver/node-resolver.test.js +0 -25
  84. package/dist/resolver/node-resolver.test.js.map +0 -1
  85. package/dist/resolver/relative-resolver.d.ts +0 -2
  86. package/dist/resolver/relative-resolver.js +0 -5
  87. package/dist/resolver/relative-resolver.js.map +0 -1
  88. package/dist/resolver/relative-resolver.test.d.ts +0 -1
  89. package/dist/resolver/relative-resolver.test.js +0 -12
  90. package/dist/resolver/relative-resolver.test.js.map +0 -1
  91. package/dist/resolver/webpack-resolver.d.ts +0 -24
  92. package/dist/resolver/webpack-resolver.js +0 -91
  93. package/dist/resolver/webpack-resolver.js.map +0 -1
  94. package/dist/resolver/webpack-resolver.test.d.ts +0 -1
  95. package/dist/resolver/webpack-resolver.test.js +0 -89
  96. package/dist/resolver/webpack-resolver.test.js.map +0 -1
  97. package/dist/runner.d.ts +0 -76
  98. package/dist/runner.js +0 -120
  99. package/dist/runner.js.map +0 -1
  100. package/dist/runner.test.d.ts +0 -1
  101. package/dist/runner.test.js +0 -287
  102. package/dist/runner.test.js.map +0 -1
  103. package/dist/test-util/jest/resolver.cjs +0 -31
  104. package/dist/test-util/jest/resolver.cjs.map +0 -1
  105. package/dist/test-util/jest/resolver.d.cts +0 -16
  106. package/dist/test-util/tsserver.d.ts +0 -31
  107. package/dist/test-util/tsserver.js +0 -112
  108. package/dist/test-util/tsserver.js.map +0 -1
  109. package/dist/test-util/util.d.ts +0 -28
  110. package/dist/test-util/util.js +0 -81
  111. package/dist/test-util/util.js.map +0 -1
  112. package/dist/transformer/index.d.ts +0 -30
  113. package/dist/transformer/index.js +0 -25
  114. package/dist/transformer/index.js.map +0 -1
  115. package/dist/transformer/index.test.d.ts +0 -1
  116. package/dist/transformer/index.test.js +0 -66
  117. package/dist/transformer/index.test.js.map +0 -1
  118. package/dist/transformer/less-transformer.d.ts +0 -2
  119. package/dist/transformer/less-transformer.js +0 -45
  120. package/dist/transformer/less-transformer.js.map +0 -1
  121. package/dist/transformer/less-transformer.test.d.ts +0 -1
  122. package/dist/transformer/less-transformer.test.js +0 -124
  123. package/dist/transformer/less-transformer.test.js.map +0 -1
  124. package/dist/transformer/postcss-transformer.d.ts +0 -12
  125. package/dist/transformer/postcss-transformer.js +0 -32
  126. package/dist/transformer/postcss-transformer.js.map +0 -1
  127. package/dist/transformer/postcss-transformer.test.d.ts +0 -1
  128. package/dist/transformer/postcss-transformer.test.js +0 -176
  129. package/dist/transformer/postcss-transformer.test.js.map +0 -1
  130. package/dist/transformer/scss-transformer.d.ts +0 -2
  131. package/dist/transformer/scss-transformer.js +0 -40
  132. package/dist/transformer/scss-transformer.js.map +0 -1
  133. package/dist/transformer/scss-transformer.test.d.ts +0 -1
  134. package/dist/transformer/scss-transformer.test.js +0 -130
  135. package/dist/transformer/scss-transformer.test.js.map +0 -1
  136. package/dist/util.d.ts +0 -21
  137. package/dist/util.js +0 -72
  138. package/dist/util.js.map +0 -1
  139. package/dist/util.test.d.ts +0 -1
  140. package/dist/util.test.js +0 -74
  141. package/dist/util.test.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happy-css-modules",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
4
4
  "description": "Creates .d.ts files from CSS Modules .css files",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -211,9 +211,9 @@ describe('generateDtsContentWithSourceMap', () => {
211
211
  });
212
212
  test('emit other directory', async () => {
213
213
  createFixtures({
214
- '/test/1.css': `.a {}`,
214
+ '/test/src/1.css': `.a {}`,
215
215
  });
216
- const result = await locator.load(filePath);
216
+ const result = await locator.load(getFixturePath('/test/src/1.css'));
217
217
  const { dtsContent, sourceMap } = generateDtsContentWithSourceMap(
218
218
  getFixturePath('/test/src/1.css'),
219
219
  getFixturePath('/test/dist/1.css.d.ts'),
@@ -224,7 +224,7 @@ describe('generateDtsContentWithSourceMap', () => {
224
224
  );
225
225
  expect(dtsContent).toMatchInlineSnapshot(`
226
226
  "declare const styles:
227
- & Readonly<Pick<(typeof import("../1.css"))["default"], "a">>
227
+ & Readonly<{ "a": string }>
228
228
  ;
229
229
  export default styles;
230
230
  "
@@ -28,18 +28,26 @@ function dashesCamelCase(str: string): string {
28
28
  }
29
29
 
30
30
  function formatTokens(tokens: Token[], localsConvention: LocalsConvention): Token[] {
31
+ function formatToken(token: Token, formatter: (str: string) => string): Token {
32
+ if ('importedName' in token && typeof token.importedName === 'string') {
33
+ return { ...token, name: formatter(token.name), importedName: formatter(token.importedName) };
34
+ } else {
35
+ return { ...token, name: formatter(token.name) };
36
+ }
37
+ }
38
+
31
39
  const result: Token[] = [];
32
40
  for (const token of tokens) {
33
41
  if (localsConvention === 'camelCaseOnly') {
34
- result.push({ ...token, name: camelcase(token.name) });
42
+ result.push(formatToken(token, camelcase));
35
43
  } else if (localsConvention === 'camelCase') {
36
44
  result.push(token);
37
- result.push({ ...token, name: camelcase(token.name) });
45
+ result.push(formatToken(token, camelcase));
38
46
  } else if (localsConvention === 'dashesOnly') {
39
- result.push({ ...token, name: dashesCamelCase(token.name) });
47
+ result.push(formatToken(token, dashesCamelCase));
40
48
  } else if (localsConvention === 'dashes') {
41
49
  result.push(token);
42
- result.push({ ...token, name: dashesCamelCase(token.name) });
50
+ result.push(formatToken(token, dashesCamelCase));
43
51
  } else {
44
52
  result.push(token); // asIs
45
53
  }
@@ -62,41 +70,55 @@ function generateTokenDeclarations(
62
70
  // This is due to the sourcemap specification. Therefore, we output multiple type definitions
63
71
  // with the same name and assign a separate original position to each.
64
72
 
65
- for (let originalLocation of token.originalLocations) {
66
- if (originalLocation.filePath === undefined) {
67
- // If the original location is not specified, fallback to the source file.
68
- originalLocation = {
69
- filePath,
70
- start: { line: 1, column: 1 },
71
- end: { line: 1, column: 1 },
72
- };
73
- }
73
+ let originalLocation = token.originalLocation;
74
+ if (originalLocation.filePath === undefined) {
75
+ // If the original location is not specified, fallback to the source file.
76
+ originalLocation = {
77
+ filePath,
78
+ start: { line: 1, column: 1 },
79
+ end: { line: 1, column: 1 },
80
+ };
81
+ }
74
82
 
75
- result.push(
76
- originalLocation.filePath === filePath || isExternalFile(originalLocation.filePath)
77
- ? new SourceNode(null, null, null, [
78
- '& Readonly<{ ',
79
- new SourceNode(
80
- originalLocation.start.line ?? null,
81
- // The SourceNode's column is 0-based, but the originalLocation's column is 1-based.
82
- originalLocation.start.column - 1 ?? null,
83
- getRelativePath(sourceMapFilePath, originalLocation.filePath),
84
- `"${token.name}"`,
85
- token.name,
86
- ),
87
- ': string }>',
88
- ])
89
- : // Imported tokens in non-external files are typed by dynamic import.
90
- // See https://github.com/mizdra/happy-css-modules/issues/106.
91
- new SourceNode(null, null, null, [
92
- '& Readonly<Pick<(typeof import(',
93
- `"${getRelativePath(filePath, originalLocation.filePath)}"`,
94
- '))["default"], ',
83
+ result.push(
84
+ originalLocation.filePath === filePath || isExternalFile(originalLocation.filePath)
85
+ ? new SourceNode(null, null, null, [
86
+ '& Readonly<{ ',
87
+ new SourceNode(
88
+ originalLocation.start.line ?? null,
89
+ // The SourceNode's column is 0-based, but the originalLocation's column is 1-based.
90
+ originalLocation.start.column - 1 ?? null,
91
+ getRelativePath(sourceMapFilePath, originalLocation.filePath),
95
92
  `"${token.name}"`,
96
- '>>',
97
- ]),
98
- );
99
- }
93
+ token.name,
94
+ ),
95
+ ': string }>',
96
+ ])
97
+ : typeof token.importedName === 'string'
98
+ ? new SourceNode(null, null, null, [
99
+ `& Readonly<{ `,
100
+ new SourceNode(
101
+ originalLocation.start.line ?? null,
102
+ // The SourceNode's column is 0-based, but the originalLocation's column is 1-based.
103
+ originalLocation.start.column - 1 ?? null,
104
+ getRelativePath(sourceMapFilePath, originalLocation.filePath),
105
+ `"${token.name}"`,
106
+ token.name,
107
+ ),
108
+ `: (typeof import(`,
109
+ `"${getRelativePath(filePath, originalLocation.filePath)}"`,
110
+ `))["default"]["${token.importedName}"] }>`,
111
+ ])
112
+ : // Imported tokens in non-external files are typed by dynamic import.
113
+ // See https://github.com/mizdra/happy-css-modules/issues/106.
114
+ new SourceNode(null, null, null, [
115
+ '& Readonly<Pick<(typeof import(',
116
+ `"${getRelativePath(filePath, originalLocation.filePath)}"`,
117
+ '))["default"], ',
118
+ `"${token.name}"`,
119
+ '>>',
120
+ ]),
121
+ );
100
122
  }
101
123
  return result;
102
124
  }
@@ -25,7 +25,7 @@ test('isSubDirectoryFile', () => {
25
25
  describe('emitGeneratedFiles', () => {
26
26
  const defaultArgs = {
27
27
  filePath: getFixturePath('/test/1.css'),
28
- tokens: [fakeToken({ name: 'foo', originalLocations: [{ start: { line: 1, column: 1 } }] })],
28
+ tokens: [fakeToken({ name: 'foo', originalLocation: { start: { line: 1, column: 1 } } })],
29
29
  emitDeclarationMap: true,
30
30
  dtsFormatOptions: undefined,
31
31
  cwd: getFixturePath('/test'),
@@ -55,7 +55,7 @@ describe('emitGeneratedFiles', () => {
55
55
  expect(await exists(getFixturePath('/test/1.css.d.ts.map'))).toBeFalsy();
56
56
  });
57
57
  test('skips writing to disk if the generated files are the same', async () => {
58
- const tokens1 = [fakeToken({ name: 'foo', originalLocations: [{ start: { line: 1, column: 1 } }] })];
58
+ const tokens1 = [fakeToken({ name: 'foo', originalLocation: { start: { line: 1, column: 1 } } })];
59
59
  await emitGeneratedFiles({ ...defaultArgs, tokens: tokens1 });
60
60
  const mtimeForDts1 = (await stat(getFixturePath('/test/1.css.d.ts'))).mtime;
61
61
  const mtimeForSourceMap1 = (await stat(getFixturePath('/test/1.css.d.ts.map'))).mtime;
@@ -68,7 +68,7 @@ describe('emitGeneratedFiles', () => {
68
68
  expect(mtimeForSourceMap1).toEqual(mtimeForSourceMap2); // skipped
69
69
 
70
70
  await waitForAsyncTask(1); // so that mtime changes.
71
- const tokens2 = [fakeToken({ name: 'bar', originalLocations: [{ start: { line: 1, column: 1 } }] })];
71
+ const tokens2 = [fakeToken({ name: 'bar', originalLocation: { start: { line: 1, column: 1 } } })];
72
72
  await emitGeneratedFiles({ ...defaultArgs, tokens: tokens2 });
73
73
  const mtimeForDts3 = (await stat(getFixturePath('/test/1.css.d.ts'))).mtime;
74
74
  const mtimeForSourceMap3 = (await stat(getFixturePath('/test/1.css.d.ts.map'))).mtime;
@@ -22,15 +22,19 @@ test('basic', async () => {
22
22
  tokens: [
23
23
  {
24
24
  name: "a",
25
- originalLocations: [
26
- { filePath: "<fixtures>/test/1.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
27
- ],
25
+ originalLocation: {
26
+ filePath: "<fixtures>/test/1.css",
27
+ start: { line: 1, column: 1 },
28
+ end: { line: 1, column: 2 },
29
+ },
28
30
  },
29
31
  {
30
32
  name: "b",
31
- originalLocations: [
32
- { filePath: "<fixtures>/test/1.css", start: { line: 2, column: 1 }, end: { line: 2, column: 2 } },
33
- ],
33
+ originalLocation: {
34
+ filePath: "<fixtures>/test/1.css",
35
+ start: { line: 2, column: 1 },
36
+ end: { line: 2, column: 2 },
37
+ },
34
38
  },
35
39
  ],
36
40
  }
@@ -74,27 +78,35 @@ test('tracks other files when `@import` is present', async () => {
74
78
  tokens: [
75
79
  {
76
80
  name: "a",
77
- originalLocations: [
78
- { filePath: "<fixtures>/test/2.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
79
- ],
81
+ originalLocation: {
82
+ filePath: "<fixtures>/test/2.css",
83
+ start: { line: 1, column: 1 },
84
+ end: { line: 1, column: 2 },
85
+ },
80
86
  },
81
87
  {
82
88
  name: "b",
83
- originalLocations: [
84
- { filePath: "<fixtures>/test/3.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
85
- ],
89
+ originalLocation: {
90
+ filePath: "<fixtures>/test/3.css",
91
+ start: { line: 1, column: 1 },
92
+ end: { line: 1, column: 2 },
93
+ },
86
94
  },
87
95
  {
88
96
  name: "c",
89
- originalLocations: [
90
- { filePath: "<fixtures>/test/4.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
91
- ],
97
+ originalLocation: {
98
+ filePath: "<fixtures>/test/4.css",
99
+ start: { line: 1, column: 1 },
100
+ end: { line: 1, column: 2 },
101
+ },
92
102
  },
93
103
  {
94
104
  name: "d",
95
- originalLocations: [
96
- { filePath: "<fixtures>/test/5-recursive.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
97
- ],
105
+ originalLocation: {
106
+ filePath: "<fixtures>/test/5-recursive.css",
107
+ start: { line: 1, column: 1 },
108
+ end: { line: 1, column: 2 },
109
+ },
98
110
  },
99
111
  ],
100
112
  }
@@ -120,16 +132,69 @@ test('does not track other files by `composes`', async () => {
120
132
  tokens: [
121
133
  {
122
134
  name: "a",
123
- originalLocations: [
124
- { filePath: "<fixtures>/test/1.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
125
- ],
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
+ `,
156
+ '/test/3.css': dedent`
157
+ @value b: 2;
158
+ `,
159
+ '/test/4.css': dedent`
160
+ @value c: 3;
161
+ `,
162
+ });
163
+ const result = await locator.load(getFixturePath('/test/1.css'));
164
+ expect(result).toMatchInlineSnapshot(`
165
+ {
166
+ dependencies: ["<fixtures>/test/2.css", "<fixtures>/test/3.css", "<fixtures>/test/4.css"],
167
+ tokens: [
168
+ {
169
+ name: "a",
170
+ originalLocation: {
171
+ filePath: "<fixtures>/test/2.css",
172
+ start: { line: 1, column: 8 },
173
+ end: { line: 1, column: 9 },
174
+ },
175
+ },
176
+ {
177
+ name: "b",
178
+ originalLocation: {
179
+ filePath: "<fixtures>/test/3.css",
180
+ start: { line: 1, column: 8 },
181
+ end: { line: 1, column: 9 },
182
+ },
183
+ },
184
+ {
185
+ name: "c",
186
+ originalLocation: {
187
+ filePath: "<fixtures>/test/4.css",
188
+ start: { line: 1, column: 8 },
189
+ end: { line: 1, column: 9 },
190
+ },
126
191
  },
127
192
  ],
128
193
  }
129
194
  `);
130
195
  });
131
196
 
132
- test('normalizes tokens', async () => {
197
+ test('unique tokens', async () => {
133
198
  createFixtures({
134
199
  '/test/1.css': dedent`
135
200
  /* duplicate import */
@@ -142,9 +207,6 @@ test('normalizes tokens', async () => {
142
207
  .a {} /* class selector that duplicates the import source */
143
208
  .b {}
144
209
  `,
145
- '/test/3.css': dedent`
146
- .c {}
147
- `,
148
210
  });
149
211
  const result = await locator.load(getFixturePath('/test/1.css'));
150
212
  expect(result).toMatchInlineSnapshot(`
@@ -153,17 +215,35 @@ test('normalizes tokens', async () => {
153
215
  tokens: [
154
216
  {
155
217
  name: "a",
156
- originalLocations: [
157
- { filePath: "<fixtures>/test/2.css", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
158
- { filePath: "<fixtures>/test/1.css", start: { line: 4, column: 1 }, end: { line: 4, column: 2 } },
159
- { filePath: "<fixtures>/test/1.css", start: { line: 5, column: 1 }, end: { line: 5, column: 2 } },
160
- ],
218
+ originalLocation: {
219
+ filePath: "<fixtures>/test/2.css",
220
+ start: { line: 1, column: 1 },
221
+ end: { line: 1, column: 2 },
222
+ },
161
223
  },
162
224
  {
163
225
  name: "b",
164
- originalLocations: [
165
- { filePath: "<fixtures>/test/2.css", start: { line: 2, column: 1 }, end: { line: 2, column: 2 } },
166
- ],
226
+ originalLocation: {
227
+ filePath: "<fixtures>/test/2.css",
228
+ start: { line: 2, column: 1 },
229
+ end: { line: 2, column: 2 },
230
+ },
231
+ },
232
+ {
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
+ },
167
247
  },
168
248
  ],
169
249
  }
@@ -233,16 +313,27 @@ describe('supports sourcemap', () => {
233
313
  tokens: [
234
314
  {
235
315
  name: "nesting",
236
- originalLocations: [
237
- { filePath: "<fixtures>/test/1.scss", start: { line: 1, column: 1 }, end: { line: 1, column: 8 } },
238
- { filePath: "<fixtures>/test/1.scss", start: { line: 3, column: 3 }, end: { line: 3, column: 10 } },
239
- ],
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
+ },
240
329
  },
241
330
  {
242
331
  name: "nesting_child",
243
- originalLocations: [
244
- { filePath: "<fixtures>/test/1.scss", start: { line: 3, column: 3 }, end: { line: 3, column: 16 } },
245
- ],
332
+ originalLocation: {
333
+ filePath: "<fixtures>/test/1.scss",
334
+ start: { line: 3, column: 3 },
335
+ end: { line: 3, column: 16 },
336
+ },
246
337
  },
247
338
  ],
248
339
  }
@@ -272,18 +363,22 @@ describe('supports sourcemap', () => {
272
363
  tokens: [
273
364
  {
274
365
  name: "selector_list_a_1",
275
- originalLocations: [
276
- { filePath: "<fixtures>/test/1.css", start: { line: 1, column: 1 }, end: { line: 1, column: 18 } },
277
- ],
366
+ originalLocation: {
367
+ filePath: "<fixtures>/test/1.css",
368
+ start: { line: 1, column: 1 },
369
+ end: { line: 1, column: 18 },
370
+ },
278
371
  },
279
372
  {
280
373
  name: "selector_list_a_2",
281
- originalLocations: [
282
- { filePath: "<fixtures>/test/1.css", start: { line: 1, column: 1 }, end: { line: 1, column: 18 } },
283
- ],
374
+ originalLocation: {
375
+ filePath: "<fixtures>/test/1.css",
376
+ start: { line: 1, column: 1 },
377
+ end: { line: 1, column: 18 },
378
+ },
284
379
  },
285
- { name: "selector_list_b_1", originalLocations: [{}] },
286
- { name: "selector_list_b_2", originalLocations: [{}] },
380
+ { name: "selector_list_b_1", originalLocation: {} },
381
+ { name: "selector_list_b_2", originalLocation: {} },
287
382
  ],
288
383
  }
289
384
  `);
@@ -4,7 +4,15 @@ import type { Resolver } from '../resolver/index.js';
4
4
  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
- import { getOriginalLocation, generateLocalTokenNames, parseAtImport, type Location, collectNodes } from './postcss.js';
7
+ import {
8
+ getOriginalLocationOfClassSelector,
9
+ getOriginalLocationOfAtValue,
10
+ generateLocalTokenNames,
11
+ parseAtImport,
12
+ type Location,
13
+ collectNodes,
14
+ parseAtValue,
15
+ } from './postcss.js';
8
16
 
9
17
  export { collectNodes, type Location } from './postcss.js';
10
18
 
@@ -20,8 +28,10 @@ function isIgnoredSpecifier(specifier: string): boolean {
20
28
  export type Token = {
21
29
  /** The token name. */
22
30
  name: string;
23
- /** The original locations of the token in the source file. */
24
- 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;
25
35
  };
26
36
 
27
37
  type CacheEntry = {
@@ -37,22 +47,6 @@ export type LoadResult = {
37
47
  tokens: Token[];
38
48
  };
39
49
 
40
- function normalizeTokens(tokens: Token[]): Token[] {
41
- const tokenNameToOriginalLocations = new Map<string, Location[]>();
42
- for (const token of tokens) {
43
- tokenNameToOriginalLocations.set(
44
- token.name,
45
- uniqueBy([...(tokenNameToOriginalLocations.get(token.name) ?? []), ...token.originalLocations], (location) =>
46
- JSON.stringify(location),
47
- ),
48
- );
49
- }
50
- return Array.from(tokenNameToOriginalLocations.entries()).map(([name, originalLocations]) => ({
51
- name,
52
- originalLocations,
53
- }));
54
- }
55
-
56
50
  export type LocatorOptions = {
57
51
  /** The function to transform source code. */
58
52
  transformer?: Transformer | undefined;
@@ -158,7 +152,7 @@ export class Locator {
158
152
 
159
153
  const tokens: Token[] = [];
160
154
 
161
- const { atImports, classSelectors } = collectNodes(ast);
155
+ const { atImports, atValues, classSelectors } = collectNodes(ast);
162
156
 
163
157
  // Load imported sheets recursively.
164
158
  for (const atImport of atImports) {
@@ -180,17 +174,51 @@ export class Locator {
180
174
  // NOTE: This method has false positives. However, it works as expected in many cases.
181
175
  if (!localTokenNames.includes(classSelector.value)) continue;
182
176
 
183
- const originalLocation = getOriginalLocation(rule, classSelector);
177
+ const originalLocation = getOriginalLocationOfClassSelector(rule, classSelector);
184
178
 
185
179
  tokens.push({
186
180
  name: classSelector.value,
187
- originalLocations: [originalLocation],
181
+ originalLocation,
188
182
  });
189
183
  }
190
184
 
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
+ }
217
+ }
218
+
191
219
  const result: LoadResult = {
192
220
  dependencies: unique(dependencies).filter((dep) => dep !== filePath),
193
- tokens: normalizeTokens(tokens),
221
+ tokens: uniqueBy(tokens, (token) => JSON.stringify(token)),
194
222
  };
195
223
  this.cache.set(filePath, { mtime, result });
196
224
  return result;