Skip to content

Commit df59568

Browse files
committed
test(vscode): add test for embedded grammars (#5861)
1 parent e768518 commit df59568

File tree

12 files changed

+1030
-14
lines changed

12 files changed

+1030
-14
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ node_modules
44
*.vsix
55

66
extensions/vscode/out
7+
extensions/vscode/tests/embeddedGrammars/*.tmLanguage.json
78
packages/*/*.d.ts
89
packages/*/*.js
910
packages/*/*.map
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { createHash } from 'node:crypto';
2+
import * as fs from 'node:fs';
3+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
4+
import * as path from 'node:path';
5+
import { fileURLToPath } from 'node:url';
6+
7+
interface LockItem {
8+
file: string;
9+
url: string;
10+
checksum?: string;
11+
}
12+
13+
const CHECKSUM_PREFIX = 'sha256-';
14+
15+
function computeChecksum(content: string | Buffer): string {
16+
const buffer = typeof content === 'string' ? Buffer.from(content) : content;
17+
return CHECKSUM_PREFIX + createHash('sha256').update(buffer).digest('hex');
18+
}
19+
20+
async function fetchWithChecksum(url: string) {
21+
console.log(`Downloading ${url}...`);
22+
const res = await fetch(url);
23+
if (!res.ok) {
24+
throw new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`);
25+
}
26+
const content = await res.text();
27+
return { content, checksum: computeChecksum(content) };
28+
}
29+
30+
async function fileChecksum(filePath: string): Promise<string | null> {
31+
if (!fs.existsSync(filePath)) {
32+
return null;
33+
}
34+
const content = await readFile(filePath);
35+
return computeChecksum(content);
36+
}
37+
38+
async function safeWriteFile(filePath: string, content: string | Buffer) {
39+
await mkdir(path.dirname(filePath), { recursive: true });
40+
await writeFile(filePath, content);
41+
}
42+
43+
async function processItem(dirPath: string, item: LockItem, update: boolean): Promise<boolean> {
44+
if (!item.file || !item.url) {
45+
throw new Error('Lock item must include "file" and "url".');
46+
}
47+
48+
const filePath = path.resolve(dirPath, item.file);
49+
50+
if (update || !item.checksum) {
51+
const { content, checksum } = await fetchWithChecksum(item.url);
52+
await safeWriteFile(filePath, content);
53+
const changed = item.checksum !== checksum;
54+
item.checksum = checksum;
55+
return changed || update;
56+
}
57+
58+
const expected = item.checksum;
59+
const localChecksum = await fileChecksum(filePath);
60+
if (localChecksum === expected) {
61+
return false;
62+
}
63+
64+
const { content, checksum } = await fetchWithChecksum(item.url);
65+
if (checksum !== expected) {
66+
throw new Error(
67+
`Checksum mismatch for ${item.file}. Expected ${expected}, got ${checksum}.
68+
Please run "pnpm test:prepare -u" to update the lock hash.`,
69+
);
70+
}
71+
72+
if (checksum !== localChecksum) {
73+
await safeWriteFile(filePath, content);
74+
}
75+
76+
return false;
77+
}
78+
79+
const dir = path.dirname(fileURLToPath(import.meta.url));
80+
const dirPath = path.resolve(dir, '../tests/embeddedGrammars');
81+
const lockPath = path.resolve(dirPath, '_lock.json');
82+
const update = !fs.existsSync(lockPath);
83+
const lock: LockItem[] = update
84+
? [
85+
{
86+
'file': 'css.tmLanguage.json',
87+
'url': 'https://raw.githubusercontent.com/microsoft/vscode/main/extensions/css/syntaxes/css.tmLanguage.json',
88+
},
89+
{
90+
'file': 'html.tmLanguage.json',
91+
'url': 'https://raw.githubusercontent.com/microsoft/vscode/main/extensions/html/syntaxes/html.tmLanguage.json',
92+
},
93+
{
94+
'file': 'javascript.tmLanguage.json',
95+
'url':
96+
'https://raw.githubusercontent.com/microsoft/vscode/main/extensions/javascript/syntaxes/JavaScript.tmLanguage.json',
97+
},
98+
{
99+
'file': 'scss.tmLanguage.json',
100+
'url': 'https://raw.githubusercontent.com/microsoft/vscode/main/extensions/scss/syntaxes/scss.tmLanguage.json',
101+
},
102+
{
103+
'file': 'typescript.tmLanguage.json',
104+
'url':
105+
'https://raw.githubusercontent.com/microsoft/vscode/main/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json',
106+
},
107+
]
108+
: JSON.parse(await readFile(lockPath, 'utf8'));
109+
if (!Array.isArray(lock)) {
110+
throw new Error('_lock.json must contain an array of lock items.');
111+
}
112+
await Promise.all(lock.map(item => processItem(dirPath, item, update)));
113+
await mkdir(dirPath, { recursive: true });
114+
await writeFile(lockPath, JSON.stringify(lock, null, '\t') + '\n', 'utf8');

extensions/vscode/tests/__snapshots__/grammar.spec.ts.snap

Lines changed: 802 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script setup lang="ts">
2+
type a = 1;
3+
</script>
4+
5+
<script setup lang="ts">
6+
type a = 1
7+
</script>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<script
2+
setup
3+
>
4+
const a = 0;
5+
</script>
6+
7+
<script
8+
setup
9+
lang="ts"
10+
>
11+
type a = 0;
12+
</script>
13+
14+
<style
15+
scoped
16+
>
17+
:root {
18+
color: red;
19+
}
20+
</style>
21+
22+
<style
23+
scoped
24+
lang="scss"
25+
>
26+
$color: red;
27+
</style>

extensions/vscode/tests/grammarFixtures/leading-operator.vue renamed to extensions/vscode/tests/embeddedGrammarFixtures/#4741-leading-operator.vue

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,12 @@
22
if (false
33
< true
44
) { }
5-
(
6-
<br />
7-
);
85
</script>
96

107
<script lang="js">
118
if (false
129
< true
1310
) {}
14-
(
15-
<br />
16-
);
1711
</script>
1812

1913
<script lang="jsx">
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script setup>
2+
const a = 0;
3+
</script>
4+
5+
<script setup lang="ts">
6+
type a = 0;
7+
</script>
8+
9+
<template>
10+
<div />
11+
</template>
12+
13+
<style scoped>
14+
:root {
15+
color: red;
16+
}
17+
</style>
18+
19+
<style scoped lang="scss">
20+
$color: red;
21+
</style>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<script setup lang="ts">const a = 1;</script>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[
2+
{
3+
"file": "css.tmLanguage.json",
4+
"url": "https://raw.githubusercontent.com/microsoft/vscode/main/extensions/css/syntaxes/css.tmLanguage.json",
5+
"checksum": "sha256-bcc97d1a3a6bf112f72d8bdb58bc438eb68aa0e070b94d00c6064b75f5cab69b"
6+
},
7+
{
8+
"file": "html.tmLanguage.json",
9+
"url": "https://raw.githubusercontent.com/microsoft/vscode/main/extensions/html/syntaxes/html.tmLanguage.json",
10+
"checksum": "sha256-80dedf4fb27e88889ac8fb72763954a6d2660502c686f4415208d8c8d00352cd"
11+
},
12+
{
13+
"file": "javascript.tmLanguage.json",
14+
"url": "https://raw.githubusercontent.com/microsoft/vscode/main/extensions/javascript/syntaxes/JavaScript.tmLanguage.json",
15+
"checksum": "sha256-db6f17f15bc4f5e860a3b8fa6055a69720a53df845c8d5121cdc4f128c16291f"
16+
},
17+
{
18+
"file": "scss.tmLanguage.json",
19+
"url": "https://raw.githubusercontent.com/microsoft/vscode/main/extensions/scss/syntaxes/scss.tmLanguage.json",
20+
"checksum": "sha256-8f2824a80a7c6fd558fc538ec52d0a7a42a4d7ecb7ddf20d79f0d1f00fa6602b"
21+
},
22+
{
23+
"file": "typescript.tmLanguage.json",
24+
"url": "https://raw.githubusercontent.com/microsoft/vscode/main/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json",
25+
"checksum": "sha256-4e92e0d7de560217d6c8d3236d85e6e17a5d77825b15729a230c761743122661"
26+
}
27+
]

extensions/vscode/tests/grammar.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ import * as path from 'node:path';
33
import { describe, expect, it } from 'vitest';
44
import { createGrammarSnapshot } from 'vscode-tmlanguage-snapshot';
55

6+
const grammarsSync = import('../scripts/grammars-sync');
67
const fixturesDir = path.resolve(__dirname, './grammarFixtures');
8+
const embeddedFixturesDir = path.resolve(__dirname, './embeddedGrammarFixtures');
79
const packageJsonPath = path.resolve(__dirname, '../package.json');
810

911
describe('grammar', async () => {
12+
await grammarsSync;
1013
const snapshot = await createGrammarSnapshot(packageJsonPath);
1114
const fixtures = fs.readdirSync(fixturesDir);
1215

@@ -18,3 +21,26 @@ describe('grammar', async () => {
1821
});
1922
}
2023
});
24+
25+
describe('embedded grammar', async () => {
26+
await grammarsSync;
27+
const embeddedGrammarsDir = path.resolve(__dirname, './embeddedGrammars');
28+
const snapshot = await createGrammarSnapshot(packageJsonPath, {
29+
extraGrammarPaths: [
30+
path.resolve(embeddedGrammarsDir, './typescript.tmLanguage.json'),
31+
path.resolve(embeddedGrammarsDir, './javascript.tmLanguage.json'),
32+
path.resolve(embeddedGrammarsDir, './css.tmLanguage.json'),
33+
path.resolve(embeddedGrammarsDir, './scss.tmLanguage.json'),
34+
path.resolve(embeddedGrammarsDir, './html.tmLanguage.json'),
35+
],
36+
});
37+
const fixtures = fs.readdirSync(embeddedFixturesDir);
38+
39+
for (const fixture of fixtures) {
40+
it(fixture, async () => {
41+
const result = await snapshot(`tests/embeddedGrammarFixtures/${fixture}`);
42+
43+
expect(result).toMatchSnapshot();
44+
});
45+
}
46+
});

0 commit comments

Comments
 (0)