happy-css-modules 3.0.0 → 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.
- package/dist/integration-test/go-to-definition.test.js +0 -35
- package/dist/integration-test/go-to-definition.test.js.map +1 -1
- package/dist/locator/index.d.ts +1 -1
- package/dist/locator/index.js +2 -17
- package/dist/locator/index.js.map +1 -1
- package/dist/locator/index.test.js +23 -119
- package/dist/locator/index.test.js.map +1 -1
- package/dist/locator/postcss.d.ts +1 -12
- package/dist/locator/postcss.js +3 -42
- package/dist/locator/postcss.js.map +1 -1
- package/dist/locator/postcss.test.js +7 -53
- package/dist/locator/postcss.test.js.map +1 -1
- package/dist/test-util/util.d.ts +1 -2
- package/dist/test-util/util.js +0 -3
- package/dist/test-util/util.js.map +1 -1
- package/dist/transformer/less-transformer.test.js +1 -9
- package/dist/transformer/less-transformer.test.js.map +1 -1
- package/dist/transformer/scss-transformer.test.js +1 -9
- package/dist/transformer/scss-transformer.test.js.map +1 -1
- package/dist/util.js +1 -1
- package/dist/util.js.map +1 -1
- package/package.json +1 -1
- package/src/integration-test/go-to-definition.test.ts +0 -35
- package/src/locator/index.test.ts +23 -124
- package/src/locator/index.ts +3 -24
- package/src/locator/postcss.test.ts +7 -74
- package/src/locator/postcss.ts +3 -44
- package/src/test-util/util.ts +1 -5
- package/src/transformer/less-transformer.test.ts +1 -9
- package/src/transformer/scss-transformer.test.ts +1 -9
- package/src/util.ts +1 -1
package/src/locator/index.ts
CHANGED
|
@@ -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
|
|
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[];
|
|
@@ -165,7 +158,7 @@ export class Locator {
|
|
|
165
158
|
|
|
166
159
|
const tokens: Token[] = [];
|
|
167
160
|
|
|
168
|
-
const { atImports, classSelectors
|
|
161
|
+
const { atImports, classSelectors } = collectNodes(ast);
|
|
169
162
|
|
|
170
163
|
// Load imported sheets recursively.
|
|
171
164
|
for (const atImport of atImports) {
|
|
@@ -195,20 +188,6 @@ export class Locator {
|
|
|
195
188
|
});
|
|
196
189
|
}
|
|
197
190
|
|
|
198
|
-
// Load imported tokens by the names recursively.
|
|
199
|
-
for (const composesDeclaration of composesDeclarations) {
|
|
200
|
-
const declarationDetail = parseComposesDeclarationWithFromUrl(composesDeclaration);
|
|
201
|
-
if (!declarationDetail) continue;
|
|
202
|
-
if (isIgnoredSpecifier(declarationDetail.from)) continue;
|
|
203
|
-
// eslint-disable-next-line no-await-in-loop
|
|
204
|
-
const from = await this.resolver(declarationDetail.from, { request: filePath });
|
|
205
|
-
// eslint-disable-next-line no-await-in-loop
|
|
206
|
-
const result = await this._load(from);
|
|
207
|
-
const externalTokens = result.tokens.filter((token) => declarationDetail.tokenNames.includes(token.name));
|
|
208
|
-
dependencies.push(from, ...result.dependencies);
|
|
209
|
-
tokens.push(...externalTokens);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
191
|
const result: LoadResult = {
|
|
213
192
|
dependencies: unique(dependencies).filter((dep) => dep !== filePath),
|
|
214
193
|
tokens: normalizeTokens(tokens),
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
import dedent from 'dedent';
|
|
2
|
-
import {
|
|
3
|
-
|
|
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";
|
|
284
|
-
.b { ignored: "ignored";
|
|
248
|
+
.a { ignored: "ignored"; }
|
|
249
|
+
.b { ignored: "ignored"; }
|
|
285
250
|
`);
|
|
286
251
|
|
|
287
|
-
const { atImports, classSelectors
|
|
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";
|
|
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";
|
|
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
|
-
});
|
package/src/locator/postcss.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
}
|
package/src/test-util/util.ts
CHANGED
|
@@ -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
|
|
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'] }[];
|
|
@@ -23,8 +23,6 @@ test('handles less features', async () => {
|
|
|
23
23
|
&_2 { dummy: ''; }
|
|
24
24
|
.b_1();
|
|
25
25
|
.b_2();
|
|
26
|
-
composes: a_1; // css module feature test (composes)
|
|
27
|
-
composes: c from './3.less'; // css module feature test (composes from other file)
|
|
28
26
|
}
|
|
29
27
|
`,
|
|
30
28
|
'/test/2.less': dedent`
|
|
@@ -40,7 +38,7 @@ test('handles less features', async () => {
|
|
|
40
38
|
// FIXME: The end position of 'a_2_2' is incorrect.
|
|
41
39
|
expect(result).toMatchInlineSnapshot(`
|
|
42
40
|
{
|
|
43
|
-
dependencies: ["<fixtures>/test/2.less"
|
|
41
|
+
dependencies: ["<fixtures>/test/2.less"],
|
|
44
42
|
tokens: [
|
|
45
43
|
{
|
|
46
44
|
name: "b_1",
|
|
@@ -72,12 +70,6 @@ test('handles less features', async () => {
|
|
|
72
70
|
{ filePath: "<fixtures>/test/1.less", start: { line: 7, column: 3 }, end: { line: 7, column: 8 } },
|
|
73
71
|
],
|
|
74
72
|
},
|
|
75
|
-
{
|
|
76
|
-
name: "c",
|
|
77
|
-
originalLocations: [
|
|
78
|
-
{ filePath: "<fixtures>/test/3.less", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
|
|
79
|
-
],
|
|
80
|
-
},
|
|
81
73
|
],
|
|
82
74
|
}
|
|
83
75
|
`);
|
|
@@ -22,8 +22,6 @@ test('handles sass features', async () => {
|
|
|
22
22
|
// sass feature test (nesting)
|
|
23
23
|
.a_2_1 { dummy: ''; }
|
|
24
24
|
&_2 { dummy: ''; }
|
|
25
|
-
composes: a_1; // css module feature test (composes)
|
|
26
|
-
composes: d from './4.scss'; // css module feature test (composes from other file)
|
|
27
25
|
}
|
|
28
26
|
`,
|
|
29
27
|
'/test/2.scss': dedent`
|
|
@@ -46,7 +44,7 @@ test('handles sass features', async () => {
|
|
|
46
44
|
// FIXME: The end position of 'a_2_2' is incorrect.
|
|
47
45
|
expect(result).toMatchInlineSnapshot(`
|
|
48
46
|
{
|
|
49
|
-
dependencies: ["<fixtures>/test/2.scss", "<fixtures>/test/3.scss"
|
|
47
|
+
dependencies: ["<fixtures>/test/2.scss", "<fixtures>/test/3.scss"],
|
|
50
48
|
tokens: [
|
|
51
49
|
{
|
|
52
50
|
name: "b_1",
|
|
@@ -85,12 +83,6 @@ test('handles sass features', async () => {
|
|
|
85
83
|
{ filePath: "<fixtures>/test/1.scss", start: { line: 8, column: 3 }, end: { line: 8, column: 8 } },
|
|
86
84
|
],
|
|
87
85
|
},
|
|
88
|
-
{
|
|
89
|
-
name: "d",
|
|
90
|
-
originalLocations: [
|
|
91
|
-
{ filePath: "<fixtures>/test/4.scss", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
|
|
92
|
-
],
|
|
93
|
-
},
|
|
94
86
|
],
|
|
95
87
|
}
|
|
96
88
|
`);
|
package/src/util.ts
CHANGED
|
@@ -66,7 +66,7 @@ export async function exists(path: string): Promise<boolean> {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export function isMatchByGlob(filePath: string, pattern: string, options: { cwd: string }): boolean {
|
|
69
|
-
return minimatch(filePath, join(options.cwd, pattern));
|
|
69
|
+
return minimatch(filePath, join(options.cwd, pattern), { windowsPathsNoEscape: true });
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
export function getPackageJson() {
|