happy-css-modules 4.0.0 → 5.0.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/LICENSE.txt +23 -0
- package/README.md +307 -2
- package/bin/hcm.js +1 -1
- package/dist/cli.d.ts +0 -4
- package/dist/cli.js +111 -118
- package/dist/cli.js.map +1 -1
- package/dist/emitter/dts.js +3 -4
- package/dist/emitter/dts.js.map +1 -1
- package/dist/emitter/dts.test.js +12 -12
- package/dist/emitter/file-system.js +2 -2
- package/dist/emitter/file-system.js.map +1 -1
- package/dist/emitter/file-system.test.js +1 -1
- package/dist/emitter/file-system.test.js.map +1 -1
- package/dist/emitter/index.js +1 -1
- package/dist/emitter/index.js.map +1 -1
- package/dist/emitter/index.test.js +1 -2
- package/dist/emitter/index.test.js.map +1 -1
- package/dist/emitter/source-map.js +1 -1
- package/dist/emitter/source-map.js.map +1 -1
- package/dist/emitter/source-map.test.js +1 -1
- package/dist/emitter/source-map.test.js.map +1 -1
- package/dist/integration-test/go-to-definition.test.js +403 -105
- package/dist/integration-test/go-to-definition.test.js.map +1 -1
- package/dist/locator/index.js +10 -8
- package/dist/locator/index.js.map +1 -1
- package/dist/locator/index.test.js +303 -169
- package/dist/locator/index.test.js.map +1 -1
- package/dist/locator/postcss.js +2 -3
- package/dist/locator/postcss.js.map +1 -1
- package/dist/locator/postcss.test.js +317 -73
- package/dist/locator/postcss.test.js.map +1 -1
- package/dist/logger.js +7 -6
- package/dist/logger.js.map +1 -1
- package/dist/regression-test/issue-168.test.js +2 -3
- package/dist/regression-test/issue-168.test.js.map +1 -1
- package/dist/resolver/index.d.ts +1 -1
- package/dist/resolver/index.js +3 -3
- package/dist/resolver/index.js.map +1 -1
- package/dist/resolver/node-resolver.js +1 -1
- package/dist/resolver/node-resolver.js.map +1 -1
- package/dist/resolver/relative-resolver.js +1 -1
- package/dist/resolver/relative-resolver.js.map +1 -1
- package/dist/resolver/webpack-resolver.d.ts +1 -1
- package/dist/resolver/webpack-resolver.js +4 -4
- package/dist/resolver/webpack-resolver.js.map +1 -1
- package/dist/runner.js +50 -18
- package/dist/runner.js.map +1 -1
- package/dist/runner.test.js +47 -32
- package/dist/runner.test.js.map +1 -1
- package/dist/test-util/line-column.d.ts +9 -0
- package/dist/test-util/line-column.js +16 -0
- package/dist/test-util/line-column.js.map +1 -0
- package/dist/test-util/line-column.test.d.ts +1 -0
- package/dist/test-util/line-column.test.js +21 -0
- package/dist/test-util/line-column.test.js.map +1 -0
- package/dist/test-util/tsserver.js +11 -12
- package/dist/test-util/tsserver.js.map +1 -1
- package/dist/test-util/util.d.ts +6 -0
- package/dist/test-util/util.js +22 -5
- package/dist/test-util/util.js.map +1 -1
- package/dist/transformer/index.js +1 -1
- package/dist/transformer/index.js.map +1 -1
- package/dist/transformer/index.test.js +17 -17
- package/dist/transformer/index.test.js.map +1 -1
- package/dist/transformer/less-transformer.js +4 -4
- package/dist/transformer/less-transformer.js.map +1 -1
- package/dist/transformer/less-transformer.test.js +75 -49
- package/dist/transformer/less-transformer.test.js.map +1 -1
- package/dist/transformer/postcss-transformer.test.js +56 -50
- package/dist/transformer/postcss-transformer.test.js.map +1 -1
- package/dist/transformer/scss-transformer.js +0 -1
- package/dist/transformer/scss-transformer.js.map +1 -1
- package/dist/transformer/scss-transformer.test.js +105 -52
- package/dist/transformer/scss-transformer.test.js.map +1 -1
- package/dist/util.js +6 -8
- package/dist/util.js.map +1 -1
- package/dist/util.test.js +2 -2
- package/dist/util.test.js.map +1 -1
- package/package.json +58 -34
- package/src/cli.ts +119 -117
- package/src/emitter/dts.test.ts +12 -12
- package/src/emitter/dts.ts +25 -26
- package/src/emitter/file-system.test.ts +1 -1
- package/src/emitter/file-system.ts +2 -2
- package/src/emitter/index.test.ts +1 -2
- package/src/emitter/index.ts +1 -1
- package/src/emitter/source-map.test.ts +1 -1
- package/src/emitter/source-map.ts +1 -1
- package/src/integration-test/go-to-definition.test.ts +405 -105
- package/src/locator/index.test.ts +303 -169
- package/src/locator/index.ts +6 -6
- package/src/locator/postcss.test.ts +317 -73
- package/src/locator/postcss.ts +2 -3
- package/src/logger.ts +6 -6
- package/src/regression-test/issue-168.test.ts +2 -3
- package/src/resolver/index.ts +4 -4
- package/src/resolver/node-resolver.ts +1 -1
- package/src/resolver/relative-resolver.ts +1 -1
- package/src/resolver/webpack-resolver.ts +5 -5
- package/src/runner.test.ts +54 -32
- package/src/runner.ts +51 -19
- package/src/test-util/line-column.test.ts +21 -0
- package/src/test-util/line-column.ts +15 -0
- package/src/test-util/tsserver.ts +11 -12
- package/src/test-util/util.ts +23 -5
- package/src/transformer/index.test.ts +17 -17
- package/src/transformer/index.ts +1 -1
- package/src/transformer/less-transformer.test.ts +73 -45
- package/src/transformer/less-transformer.ts +1 -3
- package/src/transformer/postcss-transformer.test.ts +56 -50
- package/src/transformer/scss-transformer.test.ts +107 -52
- package/src/transformer/scss-transformer.ts +0 -1
- package/src/util.test.ts +2 -2
- package/src/util.ts +6 -8
package/src/resolver/index.ts
CHANGED
|
@@ -27,7 +27,7 @@ export type DefaultResolverOptions = WebpackResolverOptions;
|
|
|
27
27
|
* @param options The options to resolve
|
|
28
28
|
* @returns The resolved path (absolute). `false` means to skip resolving.
|
|
29
29
|
*/
|
|
30
|
-
export const createDefaultResolver: (defaultResolverOptions?: DefaultResolverOptions
|
|
30
|
+
export const createDefaultResolver: (defaultResolverOptions?: DefaultResolverOptions) => Resolver = (
|
|
31
31
|
defaultResolverOptions,
|
|
32
32
|
) => {
|
|
33
33
|
const relativeResolver = createRelativeResolver();
|
|
@@ -43,14 +43,14 @@ export const createDefaultResolver: (defaultResolverOptions?: DefaultResolverOpt
|
|
|
43
43
|
return async (specifier, options) => {
|
|
44
44
|
for (const resolver of resolvers) {
|
|
45
45
|
try {
|
|
46
|
-
//
|
|
46
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
47
47
|
const resolved = await resolver(specifier, options);
|
|
48
48
|
if (resolved !== false) {
|
|
49
|
-
//
|
|
49
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
50
50
|
const isExists = await exists(resolved);
|
|
51
51
|
if (isExists) return resolved;
|
|
52
52
|
}
|
|
53
|
-
} catch
|
|
53
|
+
} catch {
|
|
54
54
|
// noop
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { basename, dirname, join, resolve } from 'path';
|
|
1
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
2
2
|
import enhancedResolve from 'enhanced-resolve';
|
|
3
3
|
import { exists } from '../util.js';
|
|
4
4
|
import type { Resolver } from './index.js';
|
|
@@ -27,7 +27,7 @@ export type WebpackResolverOptions = {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
// TODO: Support `resolve.alias` for Node.js API
|
|
30
|
-
export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOptions
|
|
30
|
+
export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOptions) => Resolver = (
|
|
31
31
|
webpackResolverOptions,
|
|
32
32
|
) => {
|
|
33
33
|
const cwd = webpackResolverOptions?.cwd ?? process.cwd();
|
|
@@ -93,7 +93,7 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
|
|
|
93
93
|
// ref: https://github.com/webpack-contrib/css-loader/blob/5e6cf91fd3f0c8b5fb4b91197b98dc56abdef4bf/src/utils.js#L92-L95
|
|
94
94
|
// ref: https://github.com/webpack-contrib/sass-loader/blob/49a578a218574ddc92a597c7e365b6c21960717e/src/utils.js#L368-L370
|
|
95
95
|
// ref: https://github.com/webpack-contrib/less-loader/blob/d74f740c100c4006b00dfb3e02c6d5aaf8713519/src/utils.js#L72-L75
|
|
96
|
-
//
|
|
96
|
+
// oxlint-disable-next-line no-param-reassign
|
|
97
97
|
if (specifier.startsWith('~')) specifier = specifier.slice(1);
|
|
98
98
|
|
|
99
99
|
for (const resolver of resolvers) {
|
|
@@ -109,11 +109,11 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
|
|
|
109
109
|
try {
|
|
110
110
|
const resolved = resolver(dirname(options.request), specifierVariant);
|
|
111
111
|
if (resolved !== false) {
|
|
112
|
-
//
|
|
112
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
113
113
|
const isExists = await exists(resolved);
|
|
114
114
|
if (isExists) return resolved;
|
|
115
115
|
}
|
|
116
|
-
} catch
|
|
116
|
+
} catch {
|
|
117
117
|
// noop
|
|
118
118
|
}
|
|
119
119
|
}
|
package/src/runner.test.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { readFile, rm, symlink, writeFile } from 'fs/promises';
|
|
2
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { readFile, rm, symlink, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
|
-
import { dirname, join, resolve } from 'path';
|
|
5
|
-
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import type { CreateCacheOptions } from '@file-cache/core';
|
|
7
7
|
import dedent from 'dedent';
|
|
8
8
|
import type { RunnerOptions, Watcher } from './runner.js';
|
|
9
|
-
import { createFixtures, exists, getFixturePath, waitForAsyncTask } from './test-util/util.js';
|
|
9
|
+
import { createFixtures, exists, getFixturePath, replaceFixtureDir, waitForAsyncTask } from './test-util/util.js';
|
|
10
10
|
|
|
11
11
|
const require = createRequire(import.meta.url);
|
|
12
12
|
|
|
@@ -22,11 +22,30 @@ vi.mock(import('@file-cache/core'), async (importOriginal) => {
|
|
|
22
22
|
};
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
+
// `createNpmPackageKey` resolves the version of `happy-css-modules` from node_modules, but this
|
|
26
|
+
// repository is the `happy-css-modules` package itself and does not install itself into node_modules.
|
|
27
|
+
// Stub it so the cache key can be computed without the package being installed.
|
|
28
|
+
vi.mock(import('@file-cache/npm'), async (importOriginal) => {
|
|
29
|
+
const fileCacheNpm = await importOriginal();
|
|
30
|
+
return {
|
|
31
|
+
...fileCacheNpm,
|
|
32
|
+
createNpmPackageKey: () => '__happy-css-modules',
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
vi.mock('sass', async (importOriginal) => {
|
|
37
|
+
const sass = await importOriginal<typeof import('sass')>();
|
|
38
|
+
return {
|
|
39
|
+
...sass,
|
|
40
|
+
// Suppress the warning: `Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.`
|
|
41
|
+
compileStringAsync: async (source: string, options?: Parameters<typeof sass.compileStringAsync>[1]) =>
|
|
42
|
+
sass.compileStringAsync(source, { silenceDeprecations: ['import'], ...options }),
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
|
|
25
46
|
const { run } = await import('./runner.js');
|
|
26
47
|
|
|
27
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
28
48
|
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
29
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
30
49
|
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
31
50
|
|
|
32
51
|
const defaultOptions: RunnerOptions = {
|
|
@@ -70,14 +89,14 @@ test('uses cache in non-watch mode', async () => {
|
|
|
70
89
|
'/test/1.css': '.a {}',
|
|
71
90
|
});
|
|
72
91
|
await run({ ...defaultOptions, declarationMap: true, logLevel: 'debug', cache: true });
|
|
73
|
-
expect(consoleLogSpy).
|
|
92
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(2);
|
|
74
93
|
expect(consoleLogSpy).toHaveBeenNthCalledWith(1, expect.anything(), expect.stringContaining('Generate .d.ts for'));
|
|
75
94
|
expect(consoleLogSpy).toHaveBeenNthCalledWith(2, expect.anything(), expect.stringContaining('generated'));
|
|
76
95
|
consoleLogSpy.mockClear();
|
|
77
96
|
|
|
78
97
|
// Skip generation
|
|
79
98
|
await run({ ...defaultOptions, declarationMap: true, logLevel: 'debug', cache: true });
|
|
80
|
-
expect(consoleLogSpy).
|
|
99
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(2);
|
|
81
100
|
expect(consoleLogSpy).toHaveBeenNthCalledWith(1, expect.anything(), expect.stringContaining('Generate .d.ts for'));
|
|
82
101
|
expect(consoleLogSpy).toHaveBeenNthCalledWith(2, expect.anything(), expect.stringContaining('skipped'));
|
|
83
102
|
consoleLogSpy.mockClear();
|
|
@@ -85,20 +104,20 @@ test('uses cache in non-watch mode', async () => {
|
|
|
85
104
|
// Generates if generated files are missing
|
|
86
105
|
await rm(getFixturePath('/test/1.css.d.ts'));
|
|
87
106
|
await run({ ...defaultOptions, declarationMap: true, logLevel: 'debug', cache: true });
|
|
88
|
-
expect(consoleLogSpy).
|
|
107
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(2);
|
|
89
108
|
expect(consoleLogSpy).toHaveBeenNthCalledWith(1, expect.anything(), expect.stringContaining('Generate .d.ts for'));
|
|
90
109
|
expect(consoleLogSpy).toHaveBeenNthCalledWith(2, expect.anything(), expect.stringContaining('generated'));
|
|
91
110
|
consoleLogSpy.mockClear();
|
|
92
111
|
|
|
93
112
|
// Generates if options are changed
|
|
94
113
|
await run({ ...defaultOptions, declarationMap: false, logLevel: 'debug', cache: true });
|
|
95
|
-
expect(consoleLogSpy).
|
|
114
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(2);
|
|
96
115
|
expect(consoleLogSpy).toHaveBeenNthCalledWith(1, expect.anything(), expect.stringContaining('Generate .d.ts for'));
|
|
97
116
|
expect(consoleLogSpy).toHaveBeenNthCalledWith(2, expect.anything(), expect.stringContaining('generated'));
|
|
98
117
|
consoleLogSpy.mockClear();
|
|
99
118
|
});
|
|
100
119
|
|
|
101
|
-
test('uses cache in watch mode', async () => {
|
|
120
|
+
test('uses cache in watch mode', { retry: 5 }, async () => {
|
|
102
121
|
createFixtures({
|
|
103
122
|
'/test/1.css': '.a-1 {}',
|
|
104
123
|
'/test/2.css': '.b-1 {}',
|
|
@@ -107,7 +126,7 @@ test('uses cache in watch mode', async () => {
|
|
|
107
126
|
// At first, process all files
|
|
108
127
|
watcher = await run({ ...defaultOptions, declarationMap: true, logLevel: 'debug', cache: true, watch: true });
|
|
109
128
|
await waitForAsyncTask(1000); // Wait until the watcher is ready
|
|
110
|
-
expect(consoleLogSpy).
|
|
129
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(3);
|
|
111
130
|
expect(consoleLogSpy).toHaveBeenNthCalledWith(
|
|
112
131
|
1,
|
|
113
132
|
expect.anything(),
|
|
@@ -128,7 +147,7 @@ test('uses cache in watch mode', async () => {
|
|
|
128
147
|
// Updating 1.css, it will only be processed
|
|
129
148
|
await writeFile(getFixturePath('/test/1.css'), '.a-2 {}');
|
|
130
149
|
await waitForAsyncTask(500); // Wait until the file is written
|
|
131
|
-
expect(consoleLogSpy).
|
|
150
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(1);
|
|
132
151
|
expect(consoleLogSpy).toHaveBeenNthCalledWith(
|
|
133
152
|
1,
|
|
134
153
|
expect.anything(),
|
|
@@ -144,10 +163,9 @@ test('uses cache in watch mode', async () => {
|
|
|
144
163
|
await waitForAsyncTask(500); // Wait until the file is written
|
|
145
164
|
|
|
146
165
|
// The updated 1.css will be processed, and the non-updated 2.css will be skipped.
|
|
147
|
-
// eslint-disable-next-line require-atomic-updates
|
|
148
166
|
watcher = await run({ ...defaultOptions, declarationMap: true, logLevel: 'debug', cache: true, watch: true });
|
|
149
167
|
await waitForAsyncTask(1000); // Wait until the watcher is ready
|
|
150
|
-
expect(consoleLogSpy).
|
|
168
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(3);
|
|
151
169
|
expect(consoleLogSpy).toHaveBeenNthCalledWith(
|
|
152
170
|
1,
|
|
153
171
|
expect.anything(),
|
|
@@ -166,7 +184,7 @@ test('outputs logs', async () => {
|
|
|
166
184
|
'/test/1.css': '.a {}',
|
|
167
185
|
});
|
|
168
186
|
await run({ ...defaultOptions, logLevel: 'debug', cache: true });
|
|
169
|
-
expect(consoleLogSpy).
|
|
187
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(2);
|
|
170
188
|
expect(consoleLogSpy).toHaveBeenNthCalledWith(1, expect.anything(), expect.stringContaining('Generate .d.ts for'));
|
|
171
189
|
expect(consoleLogSpy).toHaveBeenNthCalledWith(
|
|
172
190
|
2,
|
|
@@ -176,7 +194,7 @@ test('outputs logs', async () => {
|
|
|
176
194
|
consoleLogSpy.mockClear();
|
|
177
195
|
|
|
178
196
|
await run({ ...defaultOptions, logLevel: 'debug', cache: true });
|
|
179
|
-
expect(consoleLogSpy).
|
|
197
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(2);
|
|
180
198
|
expect(consoleLogSpy).toHaveBeenNthCalledWith(1, expect.anything(), expect.stringContaining('Generate .d.ts for'));
|
|
181
199
|
expect(consoleLogSpy).toHaveBeenNthCalledWith(2, expect.anything(), expect.stringContaining('test/1.css (skipped)'));
|
|
182
200
|
});
|
|
@@ -231,8 +249,12 @@ test('returns an error if the file fails to process in non-watch mode', async ()
|
|
|
231
249
|
const error = maybeError as AggregateError;
|
|
232
250
|
expect(error.message).toMatchInlineSnapshot(`"Failed to process files"`);
|
|
233
251
|
expect(error.errors).toHaveLength(2);
|
|
234
|
-
expect(error.errors[0]).toMatchInlineSnapshot(
|
|
235
|
-
|
|
252
|
+
expect(replaceFixtureDir((error.errors[0] as Error).message)).toMatchInlineSnapshot(
|
|
253
|
+
`"<fixtures>/test/2.css:1:1: Unknown word INVALID"`,
|
|
254
|
+
);
|
|
255
|
+
expect(replaceFixtureDir((error.errors[1] as Error).message)).toMatchInlineSnapshot(
|
|
256
|
+
`"<fixtures>/test/3.css:1:1: Unknown word INVALID"`,
|
|
257
|
+
);
|
|
236
258
|
|
|
237
259
|
// The valid files are emitted.
|
|
238
260
|
expect(await exists(getFixturePath('/test/1.css.d.ts'))).toBe(true);
|
|
@@ -242,9 +264,9 @@ describe('handles external files', () => {
|
|
|
242
264
|
beforeEach(() => {
|
|
243
265
|
createFixtures({
|
|
244
266
|
'/test/1.css': dedent`
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
267
|
+
@import './2.css';
|
|
268
|
+
@import 'external-library';
|
|
269
|
+
.a {}
|
|
248
270
|
`,
|
|
249
271
|
'/test/2.css': `.b {}`,
|
|
250
272
|
'/node_modules/external-library/index.css': `.c {}`,
|
|
@@ -275,7 +297,7 @@ test('sassLoadPaths', async () => {
|
|
|
275
297
|
const sassLoadPaths = ['test/relative'];
|
|
276
298
|
createFixtures({
|
|
277
299
|
'/test/1.scss': dedent`
|
|
278
|
-
|
|
300
|
+
@import '2.scss';
|
|
279
301
|
`,
|
|
280
302
|
'/test/relative/2.scss': `.a { dummy: ''; }`,
|
|
281
303
|
});
|
|
@@ -286,7 +308,7 @@ test('lessIncludePaths', async () => {
|
|
|
286
308
|
const lessIncludePaths = ['test/relative'];
|
|
287
309
|
createFixtures({
|
|
288
310
|
'/test/1.less': dedent`
|
|
289
|
-
|
|
311
|
+
@import '2.less';
|
|
290
312
|
`,
|
|
291
313
|
'/test/relative/2.less': `.a { dummy: ''; }`,
|
|
292
314
|
});
|
|
@@ -297,7 +319,7 @@ test('webpackResolveAlias', async () => {
|
|
|
297
319
|
const webpackResolveAlias = { '@relative': 'test/relative' };
|
|
298
320
|
createFixtures({
|
|
299
321
|
'/test/1.less': dedent`
|
|
300
|
-
|
|
322
|
+
@import '@relative/2.less';
|
|
301
323
|
`,
|
|
302
324
|
'/test/relative/2.less': `.a { dummy: ''; }`,
|
|
303
325
|
});
|
|
@@ -309,15 +331,15 @@ test('postcssConfig', async () => {
|
|
|
309
331
|
const postcssConfig = `${uuid}/postcss.config.js`;
|
|
310
332
|
createFixtures({
|
|
311
333
|
[`/${uuid}/postcss.config.js`]: dedent`
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
334
|
+
module.exports = {
|
|
335
|
+
plugins: [
|
|
336
|
+
require('${require.resolve('postcss-simple-vars')}'),
|
|
337
|
+
],
|
|
338
|
+
};
|
|
317
339
|
`,
|
|
318
340
|
'/test/1.css': dedent`
|
|
319
|
-
|
|
320
|
-
|
|
341
|
+
$prefix: foo;
|
|
342
|
+
.$(prefix)_bar {}
|
|
321
343
|
`,
|
|
322
344
|
});
|
|
323
345
|
await run({ ...defaultOptions, postcssConfig }); // not throw
|
package/src/runner.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import type { Stats } from 'node:fs';
|
|
2
|
+
import { glob } from 'node:fs/promises';
|
|
3
|
+
import { resolve, relative, basename } from 'node:path';
|
|
4
|
+
import * as process from 'node:process';
|
|
5
|
+
import { styleText } from 'node:util';
|
|
3
6
|
import { createCache } from '@file-cache/core';
|
|
4
7
|
import { createNpmPackageKey } from '@file-cache/npm';
|
|
5
|
-
import
|
|
6
|
-
import chalk from 'chalk';
|
|
7
|
-
import * as chokidar from 'chokidar';
|
|
8
|
-
import { glob } from 'glob';
|
|
8
|
+
import chokidar from 'chokidar';
|
|
9
9
|
import { DEFAULT_ARBITRARY_EXTENSIONS } from './config.js';
|
|
10
10
|
import { isGeneratedFilesExist, emitGeneratedFiles } from './emitter/index.js';
|
|
11
11
|
import { Locator } from './locator/index.js';
|
|
@@ -15,6 +15,31 @@ import { createDefaultResolver } from './resolver/index.js';
|
|
|
15
15
|
import { createDefaultTransformer, type Transformer } from './transformer/index.js';
|
|
16
16
|
import { getInstalledPeerDependencies, isMatchByGlob } from './util.js';
|
|
17
17
|
|
|
18
|
+
class Mutex {
|
|
19
|
+
private _queue: (() => void)[] = [];
|
|
20
|
+
private _locked = false;
|
|
21
|
+
|
|
22
|
+
async acquire(): Promise<void> {
|
|
23
|
+
await new Promise<void>((resolve) => {
|
|
24
|
+
if (!this._locked) {
|
|
25
|
+
this._locked = true;
|
|
26
|
+
resolve();
|
|
27
|
+
} else {
|
|
28
|
+
this._queue.push(resolve);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
release(): void {
|
|
34
|
+
const next = this._queue.shift();
|
|
35
|
+
if (next) {
|
|
36
|
+
next();
|
|
37
|
+
} else {
|
|
38
|
+
this._locked = false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
18
43
|
export type Watcher = {
|
|
19
44
|
close: () => Promise<void>;
|
|
20
45
|
};
|
|
@@ -128,7 +153,7 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
|
128
153
|
async function processFile(filePath: string) {
|
|
129
154
|
async function isChangedFile(filePath: string) {
|
|
130
155
|
const result = await cache.getAndUpdateCache(filePath);
|
|
131
|
-
//
|
|
156
|
+
// oxlint-disable-next-line typescript/only-throw-error
|
|
132
157
|
if (result.error) throw result.error;
|
|
133
158
|
return result.changed;
|
|
134
159
|
}
|
|
@@ -148,7 +173,7 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
|
148
173
|
// Generate .d.ts and .d.ts.map only when the file has been updated.
|
|
149
174
|
// However, if .d.ts or .d.ts.map has not yet been generated, always generate.
|
|
150
175
|
if (_isGeneratedFilesExist && !_isChangedFile) {
|
|
151
|
-
logger.debug(
|
|
176
|
+
logger.debug(styleText('gray', `${relative(cwd, filePath)} (skipped)`));
|
|
152
177
|
return;
|
|
153
178
|
}
|
|
154
179
|
|
|
@@ -165,7 +190,7 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
|
165
190
|
outDir: options.outDir,
|
|
166
191
|
cwd,
|
|
167
192
|
});
|
|
168
|
-
logger.info(
|
|
193
|
+
logger.info(styleText('green', `${relative(cwd, filePath)} (generated)`));
|
|
169
194
|
|
|
170
195
|
await cache.reconcile(); // Update cache for the file
|
|
171
196
|
} finally {
|
|
@@ -174,13 +199,13 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
|
174
199
|
}
|
|
175
200
|
|
|
176
201
|
async function processAllFiles() {
|
|
177
|
-
const filePaths = (await glob(options.pattern, {
|
|
202
|
+
const filePaths = (await Array.fromAsync(glob(options.pattern, { cwd })))
|
|
178
203
|
// convert relative path to absolute path
|
|
179
204
|
.map((file) => resolve(cwd, file));
|
|
180
205
|
|
|
181
206
|
const errors: unknown[] = [];
|
|
182
207
|
for (const filePath of filePaths) {
|
|
183
|
-
//
|
|
208
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
184
209
|
await processFile(filePath).catch((e) => errors.push(e));
|
|
185
210
|
}
|
|
186
211
|
|
|
@@ -196,14 +221,21 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
|
196
221
|
} else {
|
|
197
222
|
// First, watch files.
|
|
198
223
|
logger.info(`Watch ${options.pattern}...`);
|
|
199
|
-
const watcher = chokidar.watch(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
224
|
+
const watcher = chokidar.watch(cwd, {
|
|
225
|
+
ignored: (filePath: string, stats?: Stats) => {
|
|
226
|
+
// The ignored function is called twice for the same path. The first time with stats undefined,
|
|
227
|
+
// and the second time with stats provided.
|
|
228
|
+
// In the first call, we can't determine if the path is a directory or file,
|
|
229
|
+
// so we include it considering it might be a directory.
|
|
230
|
+
if (!stats) return false;
|
|
231
|
+
if (stats.isDirectory()) {
|
|
232
|
+
const name = basename(filePath);
|
|
233
|
+
return name === 'node_modules' || name === '.git';
|
|
234
|
+
}
|
|
235
|
+
return isExternalFile(filePath);
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
watcher.on('all', (eventName, filePath) => {
|
|
207
239
|
if (eventName !== 'add' && eventName !== 'change') return;
|
|
208
240
|
|
|
209
241
|
processFile(filePath).catch((e) => {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getIndexFromLineColumn } from './line-column.js';
|
|
2
|
+
|
|
3
|
+
describe('getIndexFromLineColumn', () => {
|
|
4
|
+
test('returns 0 for the first character', () => {
|
|
5
|
+
expect(getIndexFromLineColumn('abc', 1, 1)).toBe(0);
|
|
6
|
+
});
|
|
7
|
+
test('returns the index of a column in the middle of the first line', () => {
|
|
8
|
+
expect(getIndexFromLineColumn('abc', 1, 3)).toBe(2);
|
|
9
|
+
});
|
|
10
|
+
test('returns the index of the head of the second line', () => {
|
|
11
|
+
expect(getIndexFromLineColumn('abc\ndef', 2, 1)).toBe(4);
|
|
12
|
+
});
|
|
13
|
+
test('returns the index of a column in the middle of the second line', () => {
|
|
14
|
+
expect(getIndexFromLineColumn('abc\ndef', 2, 3)).toBe(6);
|
|
15
|
+
});
|
|
16
|
+
test('handles empty lines', () => {
|
|
17
|
+
// 'abc\n\ndef' => a=0 b=1 c=2 \n=3 \n=4 d=5 e=6 f=7
|
|
18
|
+
expect(getIndexFromLineColumn('abc\n\ndef', 2, 1)).toBe(4);
|
|
19
|
+
expect(getIndexFromLineColumn('abc\n\ndef', 3, 1)).toBe(5);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a 1-based line and column into a 0-based index in a string.
|
|
3
|
+
*
|
|
4
|
+
* @param str - The string to search in.
|
|
5
|
+
* @param line - The line number, 1-based.
|
|
6
|
+
* @param column - The column number, 1-based.
|
|
7
|
+
* @returns The 0-based index.
|
|
8
|
+
*/
|
|
9
|
+
export function getIndexFromLineColumn(str: string, line: number, column: number): number {
|
|
10
|
+
const offsetToLine = str
|
|
11
|
+
.split('\n')
|
|
12
|
+
.slice(0, line - 1)
|
|
13
|
+
.reduce((offset, precedingLine) => offset + precedingLine.length + 1 /* '\n'.length */, 0);
|
|
14
|
+
return offsetToLine + (column - 1);
|
|
15
|
+
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { readFileSync } from 'fs';
|
|
2
|
-
import { mkdir, writeFile as nativeWriteFile } from 'fs/promises';
|
|
3
|
-
import { dirname } from 'path';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { mkdir, writeFile as nativeWriteFile, glob } from 'node:fs/promises';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import serverHarness from '@typescript/server-harness';
|
|
6
|
-
import { glob } from 'glob';
|
|
7
6
|
import { resolve } from 'import-meta-resolve';
|
|
8
|
-
import lineColumn from 'line-column';
|
|
9
7
|
import type { server } from 'typescript/lib/tsserverlibrary.js';
|
|
8
|
+
import { getIndexFromLineColumn } from './line-column.js';
|
|
10
9
|
import { getFixturePath } from './util.js';
|
|
11
10
|
|
|
12
11
|
async function writeFile(path: string, content: string): Promise<void> {
|
|
@@ -62,7 +61,7 @@ export function createTSServer() {
|
|
|
62
61
|
const results: { identifier: string; definitions: Definition[] }[] = [];
|
|
63
62
|
|
|
64
63
|
for (let i = 0; i < identifiers.length; i++) {
|
|
65
|
-
//
|
|
64
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
66
65
|
const response: server.protocol.DefinitionResponse = await server.message({
|
|
67
66
|
seq: 0,
|
|
68
67
|
type: 'request',
|
|
@@ -76,8 +75,8 @@ export function createTSServer() {
|
|
|
76
75
|
const definitions: Definition[] = response.body!.map((definition) => {
|
|
77
76
|
const { file, start, end } = definition;
|
|
78
77
|
const fileContent = readFileSync(file, 'utf-8');
|
|
79
|
-
const startIndex =
|
|
80
|
-
const endIndex =
|
|
78
|
+
const startIndex = getIndexFromLineColumn(fileContent, start.line, start.offset);
|
|
79
|
+
const endIndex = getIndexFromLineColumn(fileContent, end.line, end.offset);
|
|
81
80
|
const text = fileContent.slice(startIndex, endIndex);
|
|
82
81
|
return { file, text, start, end };
|
|
83
82
|
});
|
|
@@ -108,8 +107,8 @@ export function createTSServer() {
|
|
|
108
107
|
const definitions: Definition[] = response.body!.map((definition) => {
|
|
109
108
|
const { file, start, end } = definition;
|
|
110
109
|
const fileContent = readFileSync(file, 'utf-8');
|
|
111
|
-
const startIndex =
|
|
112
|
-
const endIndex =
|
|
110
|
+
const startIndex = getIndexFromLineColumn(fileContent, start.line, start.offset);
|
|
111
|
+
const endIndex = getIndexFromLineColumn(fileContent, end.line, end.offset);
|
|
113
112
|
const text = fileContent.slice(startIndex, endIndex);
|
|
114
113
|
return { file, text, start, end };
|
|
115
114
|
});
|
|
@@ -120,7 +119,7 @@ export function createTSServer() {
|
|
|
120
119
|
// When a file is updated, its cache remains with the old content.
|
|
121
120
|
// Therefore we need to overwrite the cache with the latest content.
|
|
122
121
|
|
|
123
|
-
const fixtureFilePaths = await glob(getFixturePath('/**/*.ts')
|
|
122
|
+
const fixtureFilePaths = await Array.fromAsync(glob(getFixturePath('/**/*.ts')));
|
|
124
123
|
// latest contents
|
|
125
124
|
const openFiles: server.protocol.UpdateOpenRequest['arguments']['openFiles'] = fixtureFilePaths.map(
|
|
126
125
|
(filePath) => ({
|
package/src/test-util/util.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { constants, mkdirSync, realpathSync, rmSync, writeFileSync } from 'fs';
|
|
2
|
-
import { access } from 'fs/promises';
|
|
3
|
-
import { tmpdir } from 'os';
|
|
4
|
-
import { dirname, join, resolve } from 'path';
|
|
1
|
+
import { constants, mkdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { access } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
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';
|
|
@@ -63,7 +63,7 @@ export async function exists(path: string): Promise<boolean> {
|
|
|
63
63
|
try {
|
|
64
64
|
await access(path, constants.F_OK);
|
|
65
65
|
return true;
|
|
66
|
-
} catch
|
|
66
|
+
} catch {
|
|
67
67
|
return false;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -107,3 +107,21 @@ export function removeFixtures(): void {
|
|
|
107
107
|
export function getFixturePath(path: string): string {
|
|
108
108
|
return join(FIXTURE_DIR_PATH, path);
|
|
109
109
|
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Deeply clone `value` and replace all occurrences of `FIXTURE_DIR_PATH` in strings with `<fixtures>`.
|
|
113
|
+
* `FIXTURE_DIR_PATH` varies for each test run, so it must be replaced with a fixed string to make snapshots deterministic.
|
|
114
|
+
* For errors, pass `error.message` instead of the error itself.
|
|
115
|
+
*/
|
|
116
|
+
export function replaceFixtureDir<T>(value: T): T {
|
|
117
|
+
if (typeof value === 'string') {
|
|
118
|
+
return value.replaceAll(FIXTURE_DIR_PATH, '<fixtures>') as T;
|
|
119
|
+
}
|
|
120
|
+
if (Array.isArray(value)) {
|
|
121
|
+
return value.map((item) => replaceFixtureDir(item)) as T;
|
|
122
|
+
}
|
|
123
|
+
if (value !== null && typeof value === 'object') {
|
|
124
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, replaceFixtureDir(item)])) as T;
|
|
125
|
+
}
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
@@ -13,10 +13,10 @@ const locator = new Locator({ transformer: createDefaultTransformer({ cwd }) });
|
|
|
13
13
|
test('processes .scss with scss transformer', async () => {
|
|
14
14
|
createFixtures({
|
|
15
15
|
'/test/1.scss': dedent`
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
.a {
|
|
17
|
+
// scss feature test (nesting)
|
|
18
|
+
.a_1 { dummy: ''; }
|
|
19
|
+
}
|
|
20
20
|
`,
|
|
21
21
|
});
|
|
22
22
|
const result = await locator.load(getFixturePath('/test/1.scss'));
|
|
@@ -26,10 +26,10 @@ test('processes .scss with scss transformer', async () => {
|
|
|
26
26
|
test('processes .less with less transformer', async () => {
|
|
27
27
|
createFixtures({
|
|
28
28
|
'/test/1.less': dedent`
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
.a {
|
|
30
|
+
// less feature test (nesting)
|
|
31
|
+
.a_1 { dummy: ''; }
|
|
32
|
+
}
|
|
33
33
|
`,
|
|
34
34
|
});
|
|
35
35
|
const result = await locator.load(getFixturePath('/test/1.less'));
|
|
@@ -41,8 +41,8 @@ test('processes .css with postcss transformer if postcssrc is found', async () =
|
|
|
41
41
|
const locator1 = new Locator({ transformer: createDefaultTransformer() });
|
|
42
42
|
createFixtures({
|
|
43
43
|
'/test/1.css': dedent`
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
$prefix: foo;
|
|
45
|
+
.$(prefix)_bar {}
|
|
46
46
|
`,
|
|
47
47
|
});
|
|
48
48
|
const result1 = await locator1.load(getFixturePath('/test/1.css'));
|
|
@@ -55,15 +55,15 @@ test('processes .css with postcss transformer if postcssrc is found', async () =
|
|
|
55
55
|
});
|
|
56
56
|
createFixtures({
|
|
57
57
|
[`/${uuid}/postcss.config.js`]: dedent`
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
module.exports = {
|
|
59
|
+
plugins: [
|
|
60
|
+
require('${require.resolve('postcss-simple-vars')}'),
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
63
|
`,
|
|
64
64
|
'/test/1.css': dedent`
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
$prefix: foo;
|
|
66
|
+
.$(prefix)_bar {}
|
|
67
67
|
`,
|
|
68
68
|
});
|
|
69
69
|
const result2 = await locator2.load(getFixturePath('/test/1.css'));
|
package/src/transformer/index.ts
CHANGED
|
@@ -33,7 +33,7 @@ export type TransformerOptions = {
|
|
|
33
33
|
/** The function to transform source code. */
|
|
34
34
|
export type Transformer = (source: string, options: TransformerOptions) => TransformResult | Promise<TransformResult>;
|
|
35
35
|
|
|
36
|
-
//
|
|
36
|
+
// oxlint-disable-next-line no-unused-vars -- TODO: pass `e` to Error's cause option
|
|
37
37
|
export const handleImportError = (packageName: string) => (e: unknown) => {
|
|
38
38
|
throw new Error(`${packageName} import failed. Did you forget to \`npm install -D ${packageName}\`?`);
|
|
39
39
|
};
|