Skip to content

Commit c7a5ab4

Browse files
committed
fix: Add python alias coverage for langs #766
#766
1 parent fdb7ec9 commit c7a5ab4

File tree

3 files changed

+206
-1
lines changed

3 files changed

+206
-1
lines changed

extensions/langs/gen-langs-map.cjs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,9 @@ entries.push(['solidity', `() => solidity`]);
235235
entries.push(['nix', `() => nix()`]);
236236
entries.push(['svelte', `() => svelte()`]);
237237

238+
// 确保同时支持 py 和 python 键,都映射到 python() 函数
239+
entries.push(['python', `() => python()`]);
240+
238241
// 组装 import 语句
239242
const importLines = [];
240243
if (needsStreamLanguage) {
@@ -262,13 +265,25 @@ const mapObj = new Map();
262265
for (const [k, v] of entries) mapObj.set(k, v); // 后者覆盖前者
263266
const sorted = Array.from(mapObj.entries()).sort((a, b) => a[0].localeCompare(b[0]));
264267

268+
// 格式化 key 的函数
269+
function formatKey(key) {
270+
// 检查是否为有效的 JavaScript 标识符
271+
const isValidIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key);
272+
273+
if (isValidIdentifier) {
274+
return key; // 不用引号
275+
} else {
276+
return `'${key.replace(/'/g, "\\'")}'`; // 用单引号,转义内部的单引号
277+
}
278+
}
279+
265280
// 输出文件内容
266281
let out = '';
267282
out += `// auto-generated by gen-langs-map.cjs – DO NOT EDIT\n`;
268283
out += importLines.join('\n') + (importLines.length ? '\n\n' : '');
269284
out += `export const langs = {\n`;
270285
for (const [k, v] of sorted) {
271-
out += ` ${JSON.stringify(k)}: ${v},\n`;
286+
out += ` ${formatKey(k)}: ${v},\n`;
272287
}
273288
out += `} satisfies Record<string, () => LanguageSupport | StreamLanguage<unknown>>;\n\n`;
274289

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/* eslint-disable jest/no-conditional-expect */
2+
import React, { useEffect, useRef } from 'react';
3+
import { langs, langNames, loadLanguage, type LanguageName } from '..';
4+
5+
describe('@uiw/codemirror-extensions-langs', () => {
6+
describe('langs object structure', () => {
7+
it('should have both py and python keys', () => {
8+
expect(langs).toHaveProperty('py');
9+
expect(langs).toHaveProperty('python');
10+
});
11+
12+
it('should map both py and python to the same function result', () => {
13+
const pyResult = langs.py();
14+
const pythonResult = langs.python();
15+
16+
// Both should return LanguageSupport or StreamLanguage instances
17+
expect(pyResult).toBeDefined();
18+
expect(pythonResult).toBeDefined();
19+
20+
// Both types have 'language' property - StreamLanguage via getter, LanguageSupport directly
21+
expect(pyResult).toHaveProperty('language');
22+
expect(pythonResult).toHaveProperty('language');
23+
});
24+
25+
it('should contain common programming languages', () => {
26+
const expectedLanguages = ['js', 'ts', 'json', 'html', 'css', 'markdown'];
27+
28+
expectedLanguages.forEach((lang) => {
29+
expect(langs).toHaveProperty(lang);
30+
expect(typeof langs[lang as LanguageName]).toBe('function');
31+
});
32+
});
33+
34+
it('should have keys with proper formatting (no quotes for identifiers, single quotes for special)', () => {
35+
const source = require.resolve('..');
36+
const fs = require('fs');
37+
const content = fs.readFileSync(
38+
source
39+
.replace(/\.d\.ts$/, '.ts')
40+
.replace(/esm\//, 'src/')
41+
.replace(/cjs\//, 'src/'),
42+
'utf8',
43+
);
44+
45+
// Check that regular identifiers don't have quotes
46+
expect(content).toMatch(/^\s+apl:/m);
47+
expect(content).toMatch(/^\s+python:/m);
48+
expect(content).toMatch(/^\s+py:/m);
49+
expect(content).toMatch(/^\s+js:/m);
50+
51+
// Check that numbers and special characters use single quotes
52+
expect(content).toMatch(/^\s+'1':/m);
53+
expect(content).toMatch(/^\s+'c\+\+':/m);
54+
expect(content).toMatch(/^\s+'4th':/m);
55+
56+
// Ensure no double quotes are used for keys
57+
expect(content).not.toMatch(/^\s+"[^"]+"/m);
58+
});
59+
});
60+
61+
describe('loadLanguage function', () => {
62+
it('should load existing languages', () => {
63+
const result = loadLanguage('js');
64+
expect(result).toBeDefined();
65+
expect(result).toHaveProperty('language');
66+
});
67+
68+
it('should return null for non-existent languages', () => {
69+
const result = loadLanguage('nonexistent' as LanguageName);
70+
expect(result).toBeNull();
71+
});
72+
73+
it('should load python using py key', () => {
74+
const result = loadLanguage('py');
75+
expect(result).toBeDefined();
76+
expect(result).toHaveProperty('language');
77+
});
78+
79+
it('should load python using python key', () => {
80+
const result = loadLanguage('python');
81+
expect(result).toBeDefined();
82+
expect(result).toHaveProperty('language');
83+
});
84+
});
85+
86+
describe('langNames export', () => {
87+
it('should be an array containing all language names', () => {
88+
expect(Array.isArray(langNames)).toBe(true);
89+
expect(langNames.length).toBeGreaterThan(0);
90+
});
91+
92+
it('should include both py and python', () => {
93+
expect(langNames).toContain('py');
94+
expect(langNames).toContain('python');
95+
});
96+
97+
it('should maintain consistent ordering', () => {
98+
// Check that the langNames are in the same order as the generated source
99+
// The actual sorting follows JavaScript's default sort behavior
100+
const actualLangsKeys = Object.keys(langs);
101+
expect(langNames).toEqual(actualLangsKeys);
102+
});
103+
104+
it('should match the keys in langs object', () => {
105+
const langsKeys = Object.keys(langs).sort();
106+
expect(langNames.sort()).toEqual(langsKeys);
107+
});
108+
});
109+
110+
describe('key format validation', () => {
111+
it('should have consistent key patterns', () => {
112+
// Test that all numeric keys use single quotes in source
113+
const numericKeys = langNames.filter((key) => /^\d/.test(key));
114+
expect(numericKeys.length).toBeGreaterThan(0);
115+
116+
// Test that special character keys exist
117+
const specialKeys = langNames.filter((key) => /[^a-zA-Z0-9_$]/.test(key));
118+
expect(specialKeys.length).toBeGreaterThan(0);
119+
expect(specialKeys).toContain('c++');
120+
});
121+
122+
it('should validate generated code format against regex patterns', () => {
123+
// This test reads the actual generated source to ensure format
124+
const source = require.resolve('..');
125+
const fs = require('fs');
126+
const content = fs.readFileSync(
127+
source
128+
.replace(/\.d\.ts$/, '.ts')
129+
.replace(/esm\//, 'src/')
130+
.replace(/cjs\//, 'src/'),
131+
'utf8',
132+
);
133+
134+
// Find the langs object definition
135+
const langsMatch = content.match(/export const langs = \{([\s\S]*?)\}/);
136+
expect(langsMatch).toBeTruthy();
137+
138+
const langsContent = langsMatch![1];
139+
140+
// Check format rules:
141+
// 1. Valid identifiers should not have quotes
142+
const validIdentifierLines = langsContent.match(/^\s+[a-zA-Z_$][a-zA-Z0-9_$]*:/gm);
143+
expect(validIdentifierLines).toBeTruthy();
144+
expect(validIdentifierLines!.length).toBeGreaterThan(2);
145+
146+
// 2. Numbers and special chars should use single quotes
147+
const quotedLines = langsContent.match(/^\s+'[^']+'/gm);
148+
expect(quotedLines).toBeTruthy();
149+
expect(quotedLines!.length).toBeGreaterThan(5);
150+
151+
// 3. No double quotes should be used
152+
const doubleQuotedKeys = langsContent.match(/^\s+"[^"]+"/gm);
153+
expect(doubleQuotedKeys).toBeNull();
154+
});
155+
});
156+
157+
describe('language functionality', () => {
158+
it('should return valid language support objects', () => {
159+
const testLanguages: LanguageName[] = ['js', 'py', 'python', 'css', 'html'];
160+
161+
testLanguages.forEach((lang) => {
162+
const result = langs[lang]();
163+
expect(result).toBeDefined();
164+
165+
// Both LanguageSupport and StreamLanguage have 'language' property
166+
expect(result).toHaveProperty('language');
167+
168+
// Check if it's a LanguageSupport with extension property
169+
if ('extension' in result) {
170+
expect(result).toHaveProperty('extension');
171+
}
172+
});
173+
});
174+
175+
it('should handle edge case languages', () => {
176+
// Test numeric keys
177+
if ('1' in langs) {
178+
const result = langs['1' as LanguageName]();
179+
expect(result).toBeDefined();
180+
}
181+
182+
// Test special character keys
183+
if ('c++' in langs) {
184+
const result = langs['c++' as LanguageName]();
185+
expect(result).toBeDefined();
186+
}
187+
});
188+
});
189+
});

extensions/langs/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ export const langs = {
267267
pxd: () => StreamLanguage.define(cython),
268268
pxi: () => StreamLanguage.define(cython),
269269
py: () => python(),
270+
python: () => python(),
270271
pyw: () => python(),
271272
pyx: () => StreamLanguage.define(cython),
272273
q: () => StreamLanguage.define(q),

0 commit comments

Comments
 (0)