happy-css-modules 2.1.2 → 3.0.1

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 (85) hide show
  1. package/README.md +5 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/emitter/dts.js +8 -7
  4. package/dist/emitter/dts.js.map +1 -1
  5. package/dist/emitter/dts.test.js +1 -1
  6. package/dist/emitter/dts.test.js.map +1 -1
  7. package/dist/emitter/file-system.js.map +1 -1
  8. package/dist/emitter/index.js +1 -1
  9. package/dist/emitter/index.js.map +1 -1
  10. package/dist/emitter/source-map.js +2 -2
  11. package/dist/emitter/source-map.js.map +1 -1
  12. package/dist/emitter/source-map.test.js +2 -2
  13. package/dist/emitter/source-map.test.js.map +1 -1
  14. package/dist/integration-test/go-to-definition.test.js +1 -36
  15. package/dist/integration-test/go-to-definition.test.js.map +1 -1
  16. package/dist/locator/index.d.ts +1 -1
  17. package/dist/locator/index.js +6 -16
  18. package/dist/locator/index.js.map +1 -1
  19. package/dist/locator/index.test.js +23 -119
  20. package/dist/locator/index.test.js.map +1 -1
  21. package/dist/locator/postcss.d.ts +1 -12
  22. package/dist/locator/postcss.js +3 -42
  23. package/dist/locator/postcss.js.map +1 -1
  24. package/dist/locator/postcss.test.js +7 -53
  25. package/dist/locator/postcss.test.js.map +1 -1
  26. package/dist/logger.js.map +1 -1
  27. package/dist/regression-test/issue-168.test.js.map +1 -1
  28. package/dist/resolver/index.d.ts +1 -1
  29. package/dist/resolver/index.js +2 -0
  30. package/dist/resolver/index.js.map +1 -1
  31. package/dist/resolver/node-resolver.js +2 -2
  32. package/dist/resolver/node-resolver.js.map +1 -1
  33. package/dist/resolver/webpack-resolver.js +7 -8
  34. package/dist/resolver/webpack-resolver.js.map +1 -1
  35. package/dist/runner.js +9 -7
  36. package/dist/runner.js.map +1 -1
  37. package/dist/runner.test.js +16 -15
  38. package/dist/runner.test.js.map +1 -1
  39. package/dist/test-util/jest/resolver.cjs +3 -2
  40. package/dist/test-util/jest/resolver.cjs.map +1 -1
  41. package/dist/test-util/tsserver.d.ts +2 -2
  42. package/dist/test-util/tsserver.js +6 -7
  43. package/dist/test-util/tsserver.js.map +1 -1
  44. package/dist/test-util/util.d.ts +1 -2
  45. package/dist/test-util/util.js +3 -4
  46. package/dist/test-util/util.js.map +1 -1
  47. package/dist/transformer/index.d.ts +1 -1
  48. package/dist/transformer/index.js.map +1 -1
  49. package/dist/transformer/less-transformer.js +1 -0
  50. package/dist/transformer/less-transformer.js.map +1 -1
  51. package/dist/transformer/less-transformer.test.js +1 -9
  52. package/dist/transformer/less-transformer.test.js.map +1 -1
  53. package/dist/transformer/scss-transformer.js +3 -3
  54. package/dist/transformer/scss-transformer.js.map +1 -1
  55. package/dist/transformer/scss-transformer.test.js +1 -9
  56. package/dist/transformer/scss-transformer.test.js.map +1 -1
  57. package/dist/util.d.ts +1 -1
  58. package/dist/util.js +10 -7
  59. package/dist/util.js.map +1 -1
  60. package/package.json +17 -17
  61. package/src/emitter/dts.test.ts +1 -1
  62. package/src/emitter/dts.ts +10 -9
  63. package/src/emitter/index.ts +1 -1
  64. package/src/emitter/source-map.test.ts +2 -2
  65. package/src/emitter/source-map.ts +2 -2
  66. package/src/integration-test/go-to-definition.test.ts +1 -36
  67. package/src/locator/index.test.ts +23 -124
  68. package/src/locator/index.ts +7 -23
  69. package/src/locator/postcss.test.ts +7 -74
  70. package/src/locator/postcss.ts +3 -44
  71. package/src/resolver/index.ts +3 -1
  72. package/src/resolver/node-resolver.ts +2 -2
  73. package/src/resolver/webpack-resolver.ts +7 -8
  74. package/src/runner.test.ts +17 -17
  75. package/src/runner.ts +9 -8
  76. package/src/test-util/jest/resolver.cjs +3 -2
  77. package/src/test-util/tsserver.ts +7 -11
  78. package/src/test-util/util.ts +4 -6
  79. package/src/transformer/index.ts +1 -1
  80. package/src/transformer/less-transformer.test.ts +1 -9
  81. package/src/transformer/less-transformer.ts +1 -0
  82. package/src/transformer/postcss-transformer.ts +1 -1
  83. package/src/transformer/scss-transformer.test.ts +1 -9
  84. package/src/transformer/scss-transformer.ts +2 -2
  85. package/src/util.ts +10 -7
@@ -4,14 +4,7 @@ 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 {
8
- getOriginalLocation,
9
- generateLocalTokenNames,
10
- parseAtImport,
11
- type Location,
12
- parseComposesDeclarationWithFromUrl,
13
- collectNodes,
14
- } from './postcss.js';
7
+ import { getOriginalLocation, generateLocalTokenNames, parseAtImport, type Location, collectNodes } from './postcss.js';
15
8
 
16
9
  export { collectNodes, type Location } from './postcss.js';
17
10
 
@@ -38,7 +31,7 @@ type CacheEntry = {
38
31
 
39
32
  /** The result of `Locator#load`. */
40
33
  export type LoadResult = {
41
- /** The path of the file imported from the source file with `@import` or `composes`. */
34
+ /** The path of the file imported from the source file with `@import`. */
42
35
  dependencies: string[];
43
36
  /** The tokens exported by the source file. */
44
37
  tokens: Token[];
@@ -98,6 +91,7 @@ export class Locator {
98
91
  for (const dependency of dependencies) {
99
92
  const entry = this.cache.get(dependency);
100
93
  if (!entry) return true;
94
+ // eslint-disable-next-line no-await-in-loop
101
95
  const mtime = (await stat(dependency)).mtime.getTime();
102
96
  if (entry.mtime !== mtime) return true;
103
97
  }
@@ -124,7 +118,7 @@ export class Locator {
124
118
  dependencies: result.dependencies
125
119
  .map((dep) => {
126
120
  if (typeof dep === 'string') return dep;
127
- if (dep.protocol !== 'file:') throw new Error('Unsupported protocol: ' + dep.protocol);
121
+ if (dep.protocol !== 'file:') throw new Error(`Unsupported protocol: ${dep.protocol}`);
128
122
  return dep.pathname;
129
123
  })
130
124
  .filter((dep) => {
@@ -164,14 +158,16 @@ export class Locator {
164
158
 
165
159
  const tokens: Token[] = [];
166
160
 
167
- const { atImports, classSelectors, composesDeclarations } = collectNodes(ast);
161
+ const { atImports, classSelectors } = collectNodes(ast);
168
162
 
169
163
  // Load imported sheets recursively.
170
164
  for (const atImport of atImports) {
171
165
  const importedSheetPath = parseAtImport(atImport);
172
166
  if (!importedSheetPath) continue;
173
167
  if (isIgnoredSpecifier(importedSheetPath)) continue;
168
+ // eslint-disable-next-line no-await-in-loop
174
169
  const from = await this.resolver(importedSheetPath, { request: filePath });
170
+ // eslint-disable-next-line no-await-in-loop
175
171
  const result = await this._load(from);
176
172
  const externalTokens = result.tokens;
177
173
  dependencies.push(from, ...result.dependencies);
@@ -192,18 +188,6 @@ export class Locator {
192
188
  });
193
189
  }
194
190
 
195
- // Load imported tokens by the names recursively.
196
- for (const composesDeclaration of composesDeclarations) {
197
- const declarationDetail = parseComposesDeclarationWithFromUrl(composesDeclaration);
198
- if (!declarationDetail) continue;
199
- if (isIgnoredSpecifier(declarationDetail.from)) continue;
200
- const from = await this.resolver(declarationDetail.from, { request: filePath });
201
- const result = await this._load(from);
202
- const externalTokens = result.tokens.filter((token) => declarationDetail.tokenNames.includes(token.name));
203
- dependencies.push(from, ...result.dependencies);
204
- tokens.push(...externalTokens);
205
- }
206
-
207
191
  const result: LoadResult = {
208
192
  dependencies: unique(dependencies).filter((dep) => dep !== filePath),
209
193
  tokens: normalizeTokens(tokens),
@@ -1,18 +1,6 @@
1
1
  import dedent from 'dedent';
2
- import {
3
- createRoot,
4
- createClassSelectors,
5
- createAtImports,
6
- createComposesDeclarations,
7
- createFixtures,
8
- } from '../test-util/util.js';
9
- import {
10
- generateLocalTokenNames,
11
- getOriginalLocation,
12
- parseAtImport,
13
- parseComposesDeclarationWithFromUrl,
14
- collectNodes,
15
- } from './postcss.js';
2
+ import { createRoot, createClassSelectors, createAtImports, createFixtures } from '../test-util/util.js';
3
+ import { generateLocalTokenNames, getOriginalLocation, parseAtImport, collectNodes } from './postcss.js';
16
4
 
17
5
  describe('generateLocalTokenNames', () => {
18
6
  test('basic', async () => {
@@ -39,10 +27,6 @@ describe('generateLocalTokenNames', () => {
39
27
  .local_class_name_3 {}
40
28
  }
41
29
  :local(.local_class_name_4) {}
42
- .composes_target {}
43
- .composes {
44
- composes: composes_target;
45
- }
46
30
  `),
47
31
  ),
48
32
  ).toStrictEqual([
@@ -62,8 +46,6 @@ describe('generateLocalTokenNames', () => {
62
46
  'local_class_name_2',
63
47
  'local_class_name_3',
64
48
  'local_class_name_4',
65
- 'composes_target',
66
- 'composes',
67
49
  ]);
68
50
  });
69
51
  test('does not track styles imported by @import in other file because it is not a local token', async () => {
@@ -236,23 +218,6 @@ describe('getOriginalLocation', () => {
236
218
  `{ filePath: "/test/test.css", start: { line: 6, column: 8 }, end: { line: 6, column: 26 } }`,
237
219
  );
238
220
  });
239
- test('composes', () => {
240
- // eslint-disable-next-line @typescript-eslint/naming-convention
241
- const [composes_target, composes] = createClassSelectors(
242
- createRoot(dedent`
243
- .composes_target {}
244
- .composes {
245
- composes: composes_target;
246
- }
247
- `),
248
- );
249
- expect(getOriginalLocation(composes_target!.rule, composes_target!.classSelector)).toMatchInlineSnapshot(
250
- `{ filePath: "/test/test.css", start: { line: 1, column: 1 }, end: { line: 1, column: 16 } }`,
251
- );
252
- expect(getOriginalLocation(composes!.rule, composes!.classSelector)).toMatchInlineSnapshot(
253
- `{ filePath: "/test/test.css", start: { line: 2, column: 1 }, end: { line: 2, column: 9 } }`,
254
- );
255
- });
256
221
  test('with_newline', () => {
257
222
  // eslint-disable-next-line @typescript-eslint/naming-convention
258
223
  const [with_newline_1, with_newline_2, with_newline_3] = createClassSelectors(
@@ -280,23 +245,20 @@ test('collectNodes', () => {
280
245
  @import;
281
246
  @import "test.css";
282
247
  @ignored;
283
- .a { ignored: "ignored"; composes: a; }
284
- .b { ignored: "ignored"; composes: b; }
248
+ .a { ignored: "ignored"; }
249
+ .b { ignored: "ignored"; }
285
250
  `);
286
251
 
287
- const { atImports, classSelectors, composesDeclarations } = collectNodes(ast);
252
+ const { atImports, classSelectors } = collectNodes(ast);
288
253
 
289
254
  expect(atImports).toHaveLength(2);
290
255
  expect(atImports[0]!.toString()).toEqual('@import');
291
256
  expect(atImports[1]!.toString()).toEqual('@import "test.css"');
292
257
  expect(classSelectors).toHaveLength(2);
293
- expect(classSelectors[0]!.rule.toString()).toEqual('.a { ignored: "ignored"; composes: a; }');
258
+ expect(classSelectors[0]!.rule.toString()).toEqual('.a { ignored: "ignored"; }');
294
259
  expect(classSelectors[0]!.classSelector.toString()).toEqual('.a');
295
- expect(classSelectors[1]!.rule.toString()).toEqual('.b { ignored: "ignored"; composes: b; }');
260
+ expect(classSelectors[1]!.rule.toString()).toEqual('.b { ignored: "ignored"; }');
296
261
  expect(classSelectors[1]!.classSelector.toString()).toEqual('.b');
297
- expect(composesDeclarations).toHaveLength(2);
298
- expect(composesDeclarations[0]!.toString()).toEqual('composes: a');
299
- expect(composesDeclarations[1]!.toString()).toEqual('composes: b');
300
262
  });
301
263
 
302
264
  test('parseAtImport', () => {
@@ -315,32 +277,3 @@ test('parseAtImport', () => {
315
277
  expect(parseAtImport(atImports[3]!)).toBe('test.css');
316
278
  expect(parseAtImport(atImports[4]!)).toBe('test.css');
317
279
  });
318
-
319
- test('parseComposesDeclarationWithFromUrl', () => {
320
- const composesDeclarations = createComposesDeclarations(
321
- createRoot(dedent`
322
- .a {
323
- composes: a;
324
- composes: a b c;
325
- composes: a from "test.css";
326
- composes: a b c from "test.css";
327
- composes: from from from from "test.css";
328
- /* NOTE: CSS Modules do not support '... from url("test.css")'. */
329
- }
330
- `),
331
- );
332
- expect(parseComposesDeclarationWithFromUrl(composesDeclarations[0]!)).toStrictEqual(undefined);
333
- expect(parseComposesDeclarationWithFromUrl(composesDeclarations[1]!)).toStrictEqual(undefined);
334
- expect(parseComposesDeclarationWithFromUrl(composesDeclarations[2]!)).toStrictEqual({
335
- from: 'test.css',
336
- tokenNames: ['a'],
337
- });
338
- expect(parseComposesDeclarationWithFromUrl(composesDeclarations[3]!)).toStrictEqual({
339
- from: 'test.css',
340
- tokenNames: ['a', 'b', 'c'],
341
- });
342
- expect(parseComposesDeclarationWithFromUrl(composesDeclarations[4]!)).toStrictEqual({
343
- from: 'test.css',
344
- tokenNames: ['from', 'from', 'from'], // do not deduplicate.
345
- });
346
- });
@@ -54,9 +54,9 @@ export async function generateLocalTokenNames(ast: Root): Promise<string[]> {
54
54
  postcss
55
55
  .default()
56
56
  // postcss-modules collects tokens (i.e., includes external tokens) by following
57
- // the dependencies specified in the @import and composes properties.
57
+ // the dependencies specified in the @import.
58
58
  // However, we do not want `generateLocalTokenNames` to return external tokens.
59
- // So we remove the @import and composes properties beforehand.
59
+ // So we remove the @import beforehand.
60
60
  .use(removeDependenciesPlugin())
61
61
  .use(
62
62
  modules({
@@ -154,7 +154,6 @@ function isComposesDeclaration(node: Node): node is Declaration {
154
154
  type CollectNodesResult = {
155
155
  atImports: AtRule[];
156
156
  classSelectors: { rule: Rule; classSelector: ClassName }[];
157
- composesDeclarations: Declaration[];
158
157
  };
159
158
 
160
159
  /**
@@ -164,7 +163,6 @@ type CollectNodesResult = {
164
163
  export function collectNodes(ast: Root): CollectNodesResult {
165
164
  const atImports: AtRule[] = [];
166
165
  const classSelectors: { rule: Rule; classSelector: ClassName }[] = [];
167
- const composesDeclarations: Declaration[] = [];
168
166
  ast.walk((node) => {
169
167
  if (isAtImportNode(node)) {
170
168
  atImports.push(node);
@@ -183,11 +181,9 @@ export function collectNodes(ast: Root): CollectNodesResult {
183
181
  }
184
182
  });
185
183
  }).processSync(node);
186
- } else if (isComposesDeclaration(node)) {
187
- composesDeclarations.push(node);
188
184
  }
189
185
  });
190
- return { atImports, classSelectors, composesDeclarations };
186
+ return { atImports, classSelectors };
191
187
  }
192
188
 
193
189
  /**
@@ -206,40 +202,3 @@ export function parseAtImport(atImport: AtRule): string | undefined {
206
202
  }
207
203
  return undefined;
208
204
  }
209
-
210
- /**
211
- * Parse `composes` declaration with `from <url>`.
212
- * If the declaration is not found or do not have `from <url>`, return `undefined`.
213
- * @param composesDeclaration The `composes` declaration to parse.
214
- * @returns The information of the declaration.
215
- */
216
- export function parseComposesDeclarationWithFromUrl(
217
- composesDeclaration: Declaration,
218
- ): { from: string; tokenNames: string[] } | undefined {
219
- // NOTE: `composes` property syntax is...
220
- // - syntax: `composes: <class-name> [...<class-name>] [from <url>];`
221
- // - variables:
222
- // - `<class-name>`: `<sting>`
223
- // - `<url>`: `<string>`
224
- // - ref:
225
- // - https://github.com/css-modules/css-modules#composition
226
- // - https://github.com/css-modules/css-modules#composing-from-other-files
227
- // - https://github.com/css-modules/postcss-modules-extract-imports#specification
228
-
229
- const nodes = valueParser(composesDeclaration.value).nodes;
230
- if (nodes.length < 5) return undefined;
231
-
232
- const classNamesOrSpaces = nodes.slice(0, -3);
233
- const [from, , url] = nodes.slice(-3);
234
-
235
- const classNames = classNamesOrSpaces.filter((node) => node.type === 'word');
236
-
237
- // validate nodes
238
- if (from === undefined) return undefined;
239
- if (from.type !== 'word' || from.value !== 'from') return undefined;
240
- if (url === undefined) return undefined;
241
- if (url.type !== 'string') return undefined;
242
- if (classNames.length === 0) return undefined;
243
-
244
- return { from: url.value, tokenNames: classNames.map((node) => node.value) };
245
- }
@@ -11,7 +11,7 @@ export type ResolverOptions = {
11
11
  /**
12
12
  * The function to resolve the path of the imported file.
13
13
  * @returns The resolved path of the imported file. `false` means to skip resolving.
14
- * */
14
+ */
15
15
  export type Resolver = (specifier: string, options: ResolverOptions) => string | false | Promise<string | false>;
16
16
 
17
17
  export type DefaultResolverOptions = WebpackResolverOptions;
@@ -43,8 +43,10 @@ export const createDefaultResolver: (defaultResolverOptions?: DefaultResolverOpt
43
43
  return async (specifier, options) => {
44
44
  for (const resolver of resolvers) {
45
45
  try {
46
+ // eslint-disable-next-line no-await-in-loop
46
47
  const resolved = await resolver(specifier, options);
47
48
  if (resolved !== false) {
49
+ // eslint-disable-next-line no-await-in-loop
48
50
  const isExists = await exists(resolved);
49
51
  if (isExists) return resolved;
50
52
  }
@@ -2,6 +2,6 @@ import { fileURLToPath, pathToFileURL } from 'url';
2
2
  import { resolve } from 'import-meta-resolve';
3
3
  import type { Resolver } from './index.js';
4
4
 
5
- export const createNodeResolver: () => Resolver = () => async (specifier, options) => {
6
- return fileURLToPath(await resolve(specifier, pathToFileURL(options.request).href));
5
+ export const createNodeResolver: () => Resolver = () => (specifier, options) => {
6
+ return fileURLToPath(resolve(specifier, pathToFileURL(options.request).href));
7
7
  };
@@ -44,14 +44,13 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
44
44
  * @see https://github.com/webpack-contrib/css-loader/blob/897e7dd250ccdb0d31e6c66d4cd0d009f2022a85/src/plugins/postcss-import-parser.js#L228-L235
45
45
  */
46
46
  const cssLoaderResolver = enhancedResolve.create.sync({
47
- dependencyType: 'css',
48
47
  conditionNames: ['style'],
49
48
  // We are not sure how "..." affects behavior...
50
49
  mainFields: ['css', 'style', 'main', '...'],
51
50
  mainFiles: ['index', '...'],
52
51
  extensions: ['.css', '...'],
53
52
  preferRelative: true,
54
- alias: webpackResolveAlias,
53
+ alias: webpackResolveAlias ?? {},
55
54
  });
56
55
 
57
56
  /**
@@ -60,14 +59,13 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
60
59
  * @see https://github.com/webpack-contrib/sass-loader/blob/49a578a218574ddc92a597c7e365b6c21960717e/src/utils.js#L531-L539
61
60
  */
62
61
  const sassLoaderResolver = enhancedResolve.create.sync({
63
- dependencyType: 'sass',
64
62
  conditionNames: ['sass', 'style'],
65
63
  mainFields: ['sass', 'style', 'main', '...'],
66
64
  mainFiles: ['_index', 'index', '...'],
67
65
  extensions: ['.sass', '.scss', '.css'],
68
- restrictions: [/\.((sa|sc|c)ss)$/i],
66
+ restrictions: [/\.((sa|sc|c)ss)$/iu],
69
67
  preferRelative: true,
70
- alias: webpackResolveAlias,
68
+ alias: webpackResolveAlias ?? {},
71
69
  modules: ['node_modules', ...(sassLoadPaths ?? [])],
72
70
  });
73
71
 
@@ -77,13 +75,12 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
77
75
  * @see https://github.com/webpack-contrib/less-loader/blob/d74f740c100c4006b00dfb3e02c6d5aaf8713519/src/utils.js#L35-L42
78
76
  */
79
77
  const lessLoaderResolver = enhancedResolve.create.sync({
80
- dependencyType: 'less',
81
78
  conditionNames: ['less', 'style'],
82
79
  mainFields: ['less', 'style', 'main', '...'],
83
80
  mainFiles: ['index', '...'],
84
81
  extensions: ['.less', '.css'],
85
82
  preferRelative: true,
86
- alias: webpackResolveAlias,
83
+ alias: webpackResolveAlias ?? {},
87
84
  modules: ['node_modules', ...(lessIncludePaths ?? [])],
88
85
  });
89
86
 
@@ -96,6 +93,7 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
96
93
  // ref: https://github.com/webpack-contrib/css-loader/blob/5e6cf91fd3f0c8b5fb4b91197b98dc56abdef4bf/src/utils.js#L92-L95
97
94
  // ref: https://github.com/webpack-contrib/sass-loader/blob/49a578a218574ddc92a597c7e365b6c21960717e/src/utils.js#L368-L370
98
95
  // ref: https://github.com/webpack-contrib/less-loader/blob/d74f740c100c4006b00dfb3e02c6d5aaf8713519/src/utils.js#L72-L75
96
+ // eslint-disable-next-line no-param-reassign
99
97
  if (specifier.startsWith('~')) specifier = specifier.slice(1);
100
98
 
101
99
  for (const resolver of resolvers) {
@@ -104,13 +102,14 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
104
102
  ? // Support partial import for sass
105
103
  // https://sass-lang.com/documentation/at-rules/import#partials
106
104
  // https://github.com/webpack-contrib/sass-loader/blob/0e9494074f69a6b6d47efea6c083a02a31a5ae84/test/sass/import-with-underscore.sass
107
- [join(dirname(specifier), '_' + basename(specifier)), specifier]
105
+ [join(dirname(specifier), `_${basename(specifier)}`), specifier]
108
106
  : [specifier];
109
107
 
110
108
  for (const specifierVariant of specifierVariants) {
111
109
  try {
112
110
  const resolved = resolver(dirname(options.request), specifierVariant);
113
111
  if (resolved !== false) {
112
+ // eslint-disable-next-line no-await-in-loop
114
113
  const isExists = await exists(resolved);
115
114
  if (isExists) return resolved;
116
115
  }
@@ -3,23 +3,22 @@ import { randomUUID } from 'node:crypto';
3
3
  import { createRequire } from 'node:module';
4
4
  import { dirname, join, resolve } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
- import * as fileCacheNpm from '@file-cache/npm';
6
+ import * as fileCacheCore from '@file-cache/core';
7
+ import type { CreateCacheOptions } from '@file-cache/core';
7
8
  import { jest } from '@jest/globals';
8
9
  import dedent from 'dedent';
9
10
  import type { RunnerOptions, Watcher } from './runner.js';
10
11
  import { createFixtures, exists, getFixturePath, waitForAsyncTask } from './test-util/util.js';
11
12
 
12
- const packageRootDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
13
-
14
13
  const require = createRequire(import.meta.url);
15
14
 
16
- jest.unstable_mockModule('@file-cache/npm', () => ({
17
- ...fileCacheNpm, // Inherit native functions
18
- createNpmPackageKey: () => 'mocked-key',
19
- }));
20
-
21
- jest.unstable_mockModule('pkg-dir', () => ({
22
- packageDirectory: () => packageRootDir,
15
+ const uuid = randomUUID();
16
+ jest.unstable_mockModule('@file-cache/core', () => ({
17
+ ...fileCacheCore, // Inherit native functions
18
+ createCache: async (options: CreateCacheOptions) => {
19
+ options.keys.push(() => uuid); // Add a random key to avoid cache collision
20
+ return fileCacheCore.createCache(options);
21
+ },
23
22
  }));
24
23
 
25
24
  const { run } = await import('./runner.js');
@@ -144,6 +143,7 @@ test('uses cache in watch mode', async () => {
144
143
  await waitForAsyncTask(500); // Wait until the file is written
145
144
 
146
145
  // The updated 1.css will be processed, and the non-updated 2.css will be skipped.
146
+ // eslint-disable-next-line require-atomic-updates
147
147
  watcher = await run({ ...defaultOptions, declarationMap: true, logLevel: 'debug', cache: true, watch: true });
148
148
  await waitForAsyncTask(1000); // Wait until the watcher is ready
149
149
  expect(consoleLogSpy).toBeCalledTimes(3);
@@ -187,7 +187,7 @@ test('does not emit declaration map if declarationMap is false', async () => {
187
187
  });
188
188
  await run({ ...defaultOptions, declarationMap: false });
189
189
  await expect(readFile(getFixturePath('/test/1.css.d.ts'), 'utf8')).resolves.not.toThrow();
190
- await expect(readFile(getFixturePath('/test/1.css.d.ts.map'), 'utf8')).rejects.toThrow(/ENOENT/);
190
+ await expect(readFile(getFixturePath('/test/1.css.d.ts.map'), 'utf8')).rejects.toThrow(/ENOENT/u);
191
191
  });
192
192
  test('supports transformer', async () => {
193
193
  createFixtures({
@@ -205,17 +205,17 @@ test('watches for changes in files', async () => {
205
205
 
206
206
  await writeFile(getFixturePath('/test/1.css'), '.a-1 {}');
207
207
  await waitForAsyncTask(500); // Wait until the file is written
208
- expect(await readFile(getFixturePath('/test/1.css.d.ts'), 'utf8')).toMatch(/a-1/);
208
+ expect(await readFile(getFixturePath('/test/1.css.d.ts'), 'utf8')).toMatch(/a-1/u);
209
209
 
210
210
  await writeFile(getFixturePath('/test/1.css'), '.a-2 {}');
211
211
  await waitForAsyncTask(500); // Wait until the file is written
212
- expect(await readFile(getFixturePath('/test/1.css.d.ts'), 'utf8')).toMatch(/a-2/);
212
+ expect(await readFile(getFixturePath('/test/1.css.d.ts'), 'utf8')).toMatch(/a-2/u);
213
213
 
214
214
  await writeFile(getFixturePath('/test/2.css'), '.b {}');
215
215
  await writeFile(getFixturePath('/test/3.css'), '.c {}');
216
216
  await waitForAsyncTask(500); // Wait until the file is written
217
- expect(await readFile(getFixturePath('/test/2.css.d.ts'), 'utf8')).toMatch(/b/);
218
- expect(await readFile(getFixturePath('/test/3.css.d.ts'), 'utf8')).toMatch(/c/);
217
+ expect(await readFile(getFixturePath('/test/2.css.d.ts'), 'utf8')).toMatch(/b/u);
218
+ expect(await readFile(getFixturePath('/test/3.css.d.ts'), 'utf8')).toMatch(/c/u);
219
219
  });
220
220
  test('returns an error if the file fails to process in non-watch mode', async () => {
221
221
  createFixtures({
@@ -230,8 +230,8 @@ test('returns an error if the file fails to process in non-watch mode', async ()
230
230
  const error = maybeError as AggregateError;
231
231
  expect(error.message).toMatchInlineSnapshot(`"Failed to process files"`);
232
232
  expect(error.errors).toHaveLength(2);
233
- expect(error.errors[0]).toMatchInlineSnapshot(`<fixtures>/test/2.css:1:1: Unknown word`);
234
- expect(error.errors[1]).toMatchInlineSnapshot(`<fixtures>/test/3.css:1:1: Unknown word`);
233
+ expect(error.errors[0]).toMatchInlineSnapshot(`<fixtures>/test/3.css:1:1: Unknown word`);
234
+ expect(error.errors[1]).toMatchInlineSnapshot(`<fixtures>/test/2.css:1:1: Unknown word`);
235
235
 
236
236
  // The valid files are emitted.
237
237
  expect(await exists(getFixturePath('/test/1.css.d.ts'))).toBe(true);
package/src/runner.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  import { resolve, relative } from 'path';
2
2
  import * as process from 'process';
3
- import * as util from 'util';
4
3
  import { createCache } from '@file-cache/core';
5
4
  import { createNpmPackageKey } from '@file-cache/npm';
6
5
  import AwaitLock from 'await-lock';
7
6
  import chalk from 'chalk';
8
7
  import * as chokidar from 'chokidar';
9
- import _glob from 'glob';
8
+ import { glob } from 'glob';
10
9
  import { DEFAULT_ARBITRARY_EXTENSIONS } from './config.js';
11
10
  import { isGeneratedFilesExist, emitGeneratedFiles } from './emitter/index.js';
12
11
  import { Locator } from './locator/index.js';
@@ -16,8 +15,6 @@ import { createDefaultResolver } from './resolver/index.js';
16
15
  import { createDefaultTransformer, type Transformer } from './transformer/index.js';
17
16
  import { getInstalledPeerDependencies, isMatchByGlob } from './util.js';
18
17
 
19
- const glob = util.promisify(_glob);
20
-
21
18
  export type Watcher = {
22
19
  close: () => Promise<void>;
23
20
  };
@@ -94,6 +91,7 @@ type OverrideProp<T, K extends keyof T, V extends T[K]> = Omit<T, K> & { [P in K
94
91
  export async function run(options: OverrideProp<RunnerOptions, 'watch', true>): Promise<Watcher>;
95
92
  export async function run(options: RunnerOptions): Promise<void>;
96
93
  export async function run(options: RunnerOptions): Promise<Watcher | void> {
94
+ // eslint-disable-next-line new-cap
97
95
  const lock = new AwaitLock.default();
98
96
  const logger = new Logger(options.logLevel ?? 'info');
99
97
 
@@ -108,8 +106,9 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
108
106
  });
109
107
  const transformer = options.transformer ?? createDefaultTransformer({ cwd, postcssConfig: options.postcssConfig });
110
108
 
111
- const installedPeerDependencies = await getInstalledPeerDependencies();
109
+ const installedPeerDependencies = getInstalledPeerDependencies();
112
110
  const cache = await createCache({
111
+ name: 'happy-css-modules',
113
112
  mode: options.cacheStrategy ?? 'content',
114
113
  keys: [
115
114
  () => createNpmPackageKey(['happy-css-modules', ...installedPeerDependencies]),
@@ -128,6 +127,7 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
128
127
  async function processFile(filePath: string) {
129
128
  async function isChangedFile(filePath: string) {
130
129
  const result = await cache.getAndUpdateCache(filePath);
130
+ // eslint-disable-next-line @typescript-eslint/no-throw-literal
131
131
  if (result.error) throw result.error;
132
132
  return result.changed;
133
133
  }
@@ -175,6 +175,7 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
175
175
 
176
176
  const errors: unknown[] = [];
177
177
  for (const filePath of filePaths) {
178
+ // eslint-disable-next-line no-await-in-loop
178
179
  await processFile(filePath).catch((e) => errors.push(e));
179
180
  }
180
181
 
@@ -184,13 +185,13 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
184
185
  }
185
186
 
186
187
  if (!options.watch) {
187
- logger.info('Generate .d.ts for ' + options.pattern + '...');
188
+ logger.info(`Generate .d.ts for ${options.pattern}...`);
188
189
  await processAllFiles();
189
190
  // Write cache state to file for persistence
190
191
  } else {
191
192
  // First, watch files.
192
- logger.info('Watch ' + options.pattern + '...');
193
- const watcher = chokidar.watch([options.pattern.replace(/\\/g, '/')], { cwd });
193
+ logger.info(`Watch ${options.pattern}...`);
194
+ const watcher = chokidar.watch([options.pattern.replace(/\\/gu, '/')], { cwd });
194
195
  watcher.on('all', (eventName, relativeFilePath) => {
195
196
  const filePath = resolve(cwd, relativeFilePath);
196
197
 
@@ -1,3 +1,4 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
1
2
  const nativeModule = require('node:module');
2
3
 
3
4
  // workaround for https://github.com/facebook/jest/issues/12270#issuecomment-1194746382
@@ -14,7 +15,7 @@ const nativeModule = require('node:module');
14
15
  * pathFilter?: (pkg: any, path: string, relativePath: string) => string;
15
16
  * rootDir?: string;
16
17
  * }} ResolverOptions
17
- * */
18
+ */
18
19
 
19
20
  /** @type {(path: string, options: ResolverOptions) => string} */
20
21
  function resolver(module, options) {
@@ -22,7 +23,7 @@ function resolver(module, options) {
22
23
  try {
23
24
  return defaultResolver(module, options);
24
25
  // eslint-disable-next-line no-unused-vars
25
- } catch (error) {
26
+ } catch (_error) {
26
27
  return nativeModule.createRequire(basedir).resolve(module);
27
28
  }
28
29
  }
@@ -2,14 +2,12 @@ import { readFileSync } from 'fs';
2
2
  import { mkdir, writeFile as nativeWriteFile } from 'fs/promises';
3
3
  import { dirname } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
- import { promisify } from 'util';
6
5
  import serverHarness from '@typescript/server-harness';
7
- import _glob from 'glob';
6
+ import { glob } from 'glob';
8
7
  import { resolve } from 'import-meta-resolve';
9
8
  import lineColumn from 'line-column';
10
9
  import type { server } from 'typescript/lib/tsserverlibrary.js';
11
10
  import { getFixturePath } from './util.js';
12
- const glob = promisify(_glob);
13
11
 
14
12
  async function writeFile(path: string, content: string): Promise<void> {
15
13
  await mkdir(dirname(path), { recursive: true });
@@ -37,14 +35,11 @@ type Definition = {
37
35
  };
38
36
  };
39
37
 
40
- export async function createTSServer() {
41
- const server = serverHarness.launchServer(
42
- fileURLToPath(await resolve('typescript/lib/tsserver.js', import.meta.url)),
43
- [
44
- // ATA generates some extra network traffic and isn't usually relevant when profiling
45
- '--disableAutomaticTypingAcquisition',
46
- ],
47
- );
38
+ export function createTSServer() {
39
+ const server = serverHarness.launchServer(fileURLToPath(resolve('typescript/lib/tsserver.js', import.meta.url)), [
40
+ // ATA generates some extra network traffic and isn't usually relevant when profiling
41
+ '--disableAutomaticTypingAcquisition',
42
+ ]);
48
43
 
49
44
  return {
50
45
  async getIdentifierDefinitions(filePath: string, identifier: string): Promise<Definition[]> {
@@ -67,6 +62,7 @@ export async function createTSServer() {
67
62
  const results: { identifier: string; definitions: Definition[] }[] = [];
68
63
 
69
64
  for (let i = 0; i < identifiers.length; i++) {
65
+ // eslint-disable-next-line no-await-in-loop
70
66
  const response: server.protocol.DefinitionResponse = await server.message({
71
67
  seq: 0,
72
68
  type: 'request',
@@ -2,7 +2,7 @@ import { constants, mkdirSync, realpathSync, rmSync, writeFileSync } from 'fs';
2
2
  import { access } from 'fs/promises';
3
3
  import { tmpdir } from 'os';
4
4
  import { dirname, join, resolve } from 'path';
5
- import postcss, { type Root, type Rule, type AtRule, type Declaration } from 'postcss';
5
+ import postcss, { type Root, type Rule, type AtRule } from 'postcss';
6
6
  import { type ClassName } from 'postcss-selector-parser';
7
7
  import { type Token, collectNodes, type Location } from '../locator/index.js';
8
8
  import { sleepSync } from '../util.js';
@@ -25,10 +25,6 @@ export function createClassSelectors(root: Root): { rule: Rule; classSelector: C
25
25
  return collectNodes(root).classSelectors;
26
26
  }
27
27
 
28
- export function createComposesDeclarations(root: Root): Declaration[] {
29
- return collectNodes(root).composesDeclarations;
30
- }
31
-
32
28
  export function fakeToken(args: {
33
29
  name: Token['name'];
34
30
  originalLocations: { filePath?: Location['filePath']; start?: Location['start'] }[];
@@ -53,7 +49,9 @@ export function fakeToken(args: {
53
49
  }
54
50
 
55
51
  export async function waitForAsyncTask(ms?: number): Promise<void> {
56
- await new Promise((resolve) => setTimeout(resolve, ms ?? 0));
52
+ await new Promise((resolve) => {
53
+ setTimeout(resolve, ms ?? 0);
54
+ });
57
55
  }
58
56
 
59
57
  export async function exists(path: string): Promise<boolean> {
@@ -7,7 +7,7 @@ import { createScssTransformer } from './scss-transformer.js';
7
7
  /**
8
8
  * The value returned from the transformer.
9
9
  * `false` means to skip transpiling on that file.
10
- * */
10
+ */
11
11
  export type TransformResult =
12
12
  | {
13
13
  /** The transformed code. */