Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [],
"ignore": ["@css-modules-kit/content-mapper"],
"privatePackages": {
"version": true,
"tag": true
Expand Down
5 changes: 5 additions & 0 deletions .changeset/export-token-utilities.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@css-modules-kit/core': minor
---

feat(core): export `validateTokenName`, `isURLSpecifier`, and token reference types
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,4 @@ Cargo.lock

### User
/crates/zed/extension.wasm
/.tmp/
24 changes: 24 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,30 @@
"TSS_DEBUG": "5859"
}
},
{
// Launches the TypeScript Native Preview extension built from the pinned
// microsoft/TypeScript commit. The marketplace build predates the content
// mapper support, so the extension must be run from source.
"name": "tsgo (7-content-mapper)",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/.tmp/typescript/packages/vscode-typescript",
"--profile-temp",
"--skip-welcome",
// The extension enables content mappers only in a trusted workspace. Disabling
// workspace trust makes VS Code treat every workspace as trusted, which also
// skips the trust dialog on launch.
"--disable-workspace-trust",
"--folder-uri=${workspaceFolder}/examples/7-content-mapper",
"${workspaceFolder}/examples/7-content-mapper/src/index.ts"
],
"outFiles": ["${workspaceFolder}/.tmp/typescript/packages/vscode-typescript/dist/**/*.js"],
"preLaunchTask": "prepare content-mapper example",
"presentation": {
"group": "tsgo"
}
},
{
"name": "vscode-test",
"type": "extensionHost",
Expand Down
23 changes: 23 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,29 @@
"cwd": "${workspaceFolder}/packages/vscode"
},
"group": "build"
},
{
"label": "vp: build - packages/content-mapper",
"type": "shell",
"command": "vp run build",
"options": {
"cwd": "${workspaceFolder}/packages/content-mapper"
},
"group": "build"
},
{
"label": "setup tsgo extension",
"type": "shell",
"command": "./scripts/setup-tsgo-extension.sh",
"options": {
"cwd": "${workspaceFolder}"
},
"group": "build"
},
{
"label": "prepare content-mapper example",
"dependsOn": ["vp: build - packages/content-mapper", "setup tsgo extension"],
"group": "build"
}
]
}
3 changes: 3 additions & 0 deletions examples/7-content-mapper/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"js/ts.experimental.useTsgo": true
}
14 changes: 14 additions & 0 deletions examples/7-content-mapper/src/a.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@import './b.module.css';
@value primary: #2864f0;

.a_1 {
color: primary;
composes: b_1 from './b.module.css';
animation-name: fade-in;
}

@keyframes fade-in {
from {
opacity: 0;
}
}
3 changes: 3 additions & 0 deletions examples/7-content-mapper/src/b.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.b_1 {
color: blue;
}
3 changes: 3 additions & 0 deletions examples/7-content-mapper/src/global.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* {
margin: 0;
}
8 changes: 8 additions & 0 deletions examples/7-content-mapper/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import './global.css';
import styles from './a.module.css';

styles.a_1;
styles.b_1;
styles.primary;
styles['fade-in'];
styles.unknown; // Expected TS2339 error
19 changes: 19 additions & 0 deletions examples/7-content-mapper/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"target": "es2015",
"lib": ["ES2015"],
"module": "Preserve",
"moduleResolution": "bundler",

"noEmit": true,
"incremental": false,
"types": [] // Simplify tsserver.log
},
"contentMappers": [
{
"package": "@css-modules-kit/content-mapper",
"extensions": [".css"]
}
]
}
78 changes: 78 additions & 0 deletions packages/content-mapper/e2e-test/diagnostics.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import dedent from 'dedent';
import { describe, expect, test } from 'vite-plus/test';
import { buildStylesImport, buildTSConfigJSON } from './test-util/builder.js';
import { fixtureDir, setupFixture } from './test-util/fixture.js';
import { launchLSPClient } from './test-util/lsp-client.js';

const client = launchLSPClient(fixtureDir);

describe.each([{ namedExports: false }, { namedExports: true }])('namedExports: $namedExports', ({ namedExports }) => {
test('reports an unknown property access on a styles binding', async () => {
const { iff, getRange } = await setupFixture({
'tsconfig.json': buildTSConfigJSON({ mapperOptions: { namedExports } }),
'index.ts': dedent`
${buildStylesImport('./a.module.css', { namedExports })}
styles.unknown;
`,
'a.module.css': `.a_1 { color: red; }`,
});
await client.openFile(iff.paths['index.ts']);

const report = await client.sendDocumentDiagnostic(iff.paths['index.ts']);

expect(report.items).toStrictEqual([
expect.objectContaining({ code: 2339, range: getRange('index.ts', 'unknown') }),
]);
});

test('provides the mapper-generated type on the styles binding', async () => {
const { iff } = await setupFixture({
'tsconfig.json': buildTSConfigJSON({ mapperOptions: { namedExports } }),
'index.ts': dedent`
${buildStylesImport('./a.module.css', { namedExports })}
type Expected = { a_1: string };
export const _t: Expected = styles;
`,
'a.module.css': `.a_1 { color: red; }`,
});
await client.openFile(iff.paths['index.ts']);

const report = await client.sendDocumentDiagnostic(iff.paths['index.ts']);

expect(report.items).toStrictEqual([]);
});

// NOTE: Unlike ts-plugin, which reports its own "Cannot import module" diagnostic on the bare
// path, the unresolvable import is reported by TypeScript itself (TS2307) on the quoted
// specifier.
test('reports a semantic diagnostic on a CSS module file', async () => {
const { iff, getRange } = await setupFixture({
'tsconfig.json': buildTSConfigJSON({ mapperOptions: { namedExports } }),
'a.module.css': `@import './unresolvable.module.css';`,
});
await client.openFile(iff.paths['a.module.css']);

const report = await client.sendDocumentDiagnostic(iff.paths['a.module.css']);

expect(report.items).toStrictEqual([
expect.objectContaining({ code: 2307, range: getRange('a.module.css', `'./unresolvable.module.css'`) }),
]);
});

test('reports a syntactic diagnostic on a CSS module file', async () => {
const { iff, getRange } = await setupFixture({
'tsconfig.json': buildTSConfigJSON({ mapperOptions: { namedExports } }),
'a.module.css': `@value;`,
});
await client.openFile(iff.paths['a.module.css']);

const report = await client.sendDocumentDiagnostic(iff.paths['a.module.css']);

expect(report.items).toStrictEqual([
expect.objectContaining({
message: '`@value` is a invalid syntax.',
range: getRange('a.module.css', '@value;'),
}),
]);
});
});
60 changes: 60 additions & 0 deletions packages/content-mapper/e2e-test/file-events.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import dedent from 'dedent';
import { describe, expect, test } from 'vite-plus/test';
import { buildStylesImport, buildTSConfigJSON } from './test-util/builder.js';
import { fixtureDir, setupFixture } from './test-util/fixture.js';
import { launchLSPClient } from './test-util/lsp-client.js';

const client = launchLSPClient(fixtureDir);

describe.each([{ namedExports: false }, { namedExports: true }])('namedExports: $namedExports', ({ namedExports }) => {
describe('when adding a CSS module', () => {
test("updates the importer's diagnostic when a CSS module is added", async () => {
const { iff, getRange } = await setupFixture({
'tsconfig.json': buildTSConfigJSON({ mapperOptions: { namedExports } }),
'index.ts': dedent`
${buildStylesImport('./a.module.css', { namedExports })}
styles.a_1;
`,
});
await client.openFile(iff.paths['index.ts']);

const before = await client.sendDocumentDiagnostic(iff.paths['index.ts']);
expect(before.items).toStrictEqual([
expect.objectContaining({ code: 2307, range: getRange('index.ts', `'./a.module.css'`) }),
]);

await iff.addFixtures({ 'a.module.css': '.a_1 { color: red; }' });
await client.openFile(iff.join('a.module.css'));

const after = await client.sendDocumentDiagnostic(iff.paths['index.ts']);
expect(after.items).toStrictEqual([]);
});
});

describe('when updating a CSS module', () => {
test("updates the importer's diagnostic when a CSS module is modified", async () => {
const { iff, getRange } = await setupFixture({
'tsconfig.json': buildTSConfigJSON({ mapperOptions: { namedExports } }),
'index.ts': dedent`
${buildStylesImport('./a.module.css', { namedExports })}
styles.a_1;
`,
'a.module.css': '',
});
await client.openFile(iff.paths['index.ts']);

const before = await client.sendDocumentDiagnostic(iff.paths['index.ts']);
expect(before.items).toStrictEqual([expect.objectContaining({ code: 2339, range: getRange('index.ts', 'a_1') })]);

await client.openFile(iff.paths['a.module.css']);
await client.changeFile(iff.paths['a.module.css'], `.a_1 {}`);

const after = await client.sendDocumentDiagnostic(iff.paths['index.ts']);
expect(after.items).toStrictEqual([]);
});
});

describe('when removing a CSS module', () => {
test.todo("updates the importer's diagnostic when a CSS module is removed");
});
});
Loading
Loading