Skip to content

Commit 089809e

Browse files
authored
Merge pull request #7 from jaseci-labs/esc_color_fix
Fix: Esc KW syntax highlighting
2 parents 1f9b800 + 48adaa6 commit 089809e

5 files changed

Lines changed: 311 additions & 226 deletions

File tree

examples/app.jac

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,35 @@
1+
cl {
2+
3+
def app() -> any {
4+
[count, setCount] = useState(0);
5+
return <div>
6+
<h1>Hello, World!</h1>
7+
<p>Count: {count}</p>
8+
<button onClick={lambda e: any -> None { setCount(count + 1); }}>
9+
Increment
10+
</button>
11+
<ButtonComponent label="Click Me" />
12+
<NavLink to="/about">About Page</NavLink>
13+
</div>;
14+
}
15+
}
16+
cl{
17+
18+
def app{
19+
a = <>esc;
20+
21+
fragment = (
22+
<>
23+
<div>
24+
First
25+
</div>
26+
<div>
27+
Second
28+
</div>
29+
</>
30+
);
31+
}
32+
}
133

234

335

@@ -8,7 +40,9 @@ with entry{
840

941
cl import from react {useState}
1042

43+
1144
cl {
45+
1246
def app() -> any {
1347
let [count, setCount] = useState(0);
1448
return <div>
Lines changed: 207 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,214 @@
11
/*
22
* Jest tests for inspectTokenScopesHandler functionality in VSCode extension.
33
* Uses real vscode-textmate and vscode-oniguruma libraries for actual grammar testing.
4+
*
5+
* Token locations use format: "line:startCol-endCol" (1-based)
46
*/
57

68
import * as path from 'path';
79
import * as fs from 'fs';
8-
import { tokenizeContent } from '../commands/inspectTokenScopes';
9-
10-
// Read the actual app.jac file
11-
const appJacPath = path.join(process.cwd(), 'examples', 'app.jac');
12-
const appJacContent = fs.readFileSync(appJacPath, 'utf-8');
13-
14-
// Paths for grammar and wasm
15-
const grammarPath = path.join(process.cwd(), 'syntaxes', 'jac.tmLanguage.json');
16-
const wasmPath = path.join(process.cwd(), 'node_modules', 'vscode-oniguruma', 'release', 'onig.wasm');
17-
18-
describe('inspectTokenScopesHandler', () => {
19-
test('should tokenize Jac keywords correctly', async () => {
20-
const tokenMap = await tokenizeContent(appJacContent, grammarPath, wasmPath);
21-
22-
// Check 'with' keyword (storage.type.function.jac)
23-
const withScopes = tokenMap.get('with');
24-
expect(withScopes).toBeDefined();
25-
expect(withScopes).toContain('source.jac');
26-
expect(withScopes).toContain('storage.type.function.jac');
27-
28-
// Check 'entry' keyword
29-
const entryScopes = tokenMap.get('entry');
30-
expect(entryScopes).toBeDefined();
31-
expect(entryScopes).toContain('keyword.control.flow.jac');
32-
33-
// Check 'lambda' keyword
34-
const lambdaScopes = tokenMap.get('lambda');
35-
expect(lambdaScopes).toBeDefined();
36-
expect(lambdaScopes).toContain('keyword.control.flow.jac');
37-
38-
// Check 'print' builtin
39-
const printScopes = tokenMap.get('print');
40-
expect(printScopes).toBeDefined();
41-
expect(printScopes).toContain('support.function.builtin.jac');
42-
});
43-
44-
test('should tokenize JSX elements correctly', async () => {
45-
const tokenMap = await tokenizeContent(appJacContent, grammarPath, wasmPath);
46-
47-
// Check that we have JSX-related tokens
48-
const allScopes = Array.from(tokenMap.values()).flat();
49-
50-
// Check for meta.jsx scope or entity.name.tag for JSX elements
51-
const hasJsxScopes = allScopes.some(s =>
52-
s.includes('jsx') ||
53-
s.includes('entity.name.tag') ||
54-
s.includes('punctuation.definition.tag')
55-
);
56-
expect(hasJsxScopes).toBe(true);
57-
58-
// Check specific JSX HTML tags
59-
const divScopes = tokenMap.get('div');
60-
expect(divScopes).toBeDefined();
61-
expect(divScopes).toContain('entity.name.tag.html.jsx.jac');
62-
63-
const h1Scopes = tokenMap.get('h1');
64-
expect(h1Scopes).toBeDefined();
65-
expect(h1Scopes).toContain('entity.name.tag.html.jsx.jac');
66-
67-
const buttonScopes = tokenMap.get('button');
68-
expect(buttonScopes).toBeDefined();
69-
expect(buttonScopes).toContain('entity.name.tag.html.jsx.jac');
70-
71-
// Check JSX attributes
72-
const onClickScopes = tokenMap.get('onClick');
73-
expect(onClickScopes).toBeDefined();
74-
expect(onClickScopes).toContain('entity.other.attribute-name.jsx.jac');
75-
76-
// Check PascalCase component names
77-
const buttonComponentScopes = tokenMap.get('ButtonComponent');
78-
expect(buttonComponentScopes).toBeDefined();
79-
expect(buttonComponentScopes).toContain('support.class.component.jsx.jac');
80-
81-
const navLinkScopes = tokenMap.get('NavLink');
82-
expect(navLinkScopes).toBeDefined();
83-
expect(navLinkScopes).toContain('support.class.component.jsx.jac');
84-
});
85-
});
10+
import {
11+
tokenizeContent,
12+
TokenizeResult,
13+
TokenInfo
14+
} from '../commands/inspectTokenScopes';
15+
16+
/** Get token at a specific location */
17+
function getTokenByLocation(
18+
result: TokenizeResult,
19+
line: number,
20+
startCol: number,
21+
endCol: number
22+
): TokenInfo | undefined {
23+
return result.byLocation.get(`${line}:${startCol}-${endCol}`);
24+
}
25+
26+
// Test fixture paths
27+
const EXAMPLES_DIR = path.join(process.cwd(), 'examples');
28+
const GRAMMAR_PATH = path.join(process.cwd(), 'syntaxes', 'jac.tmLanguage.json');
29+
const WASM_PATH = path.join(process.cwd(), 'node_modules', 'vscode-oniguruma', 'release', 'onig.wasm');
30+
31+
// Load test fixture
32+
const appJacContent = fs.readFileSync(path.join(EXAMPLES_DIR, 'app.jac'), 'utf-8');
33+
34+
/**
35+
* Helper to assert a token has expected text and contains expected scopes
36+
*/
37+
function expectToken(
38+
result: TokenizeResult,
39+
line: number,
40+
startCol: number,
41+
endCol: number,
42+
expectedText: string,
43+
expectedScopes: string[]
44+
): void {
45+
const token = getTokenByLocation(result, line, startCol, endCol);
46+
expect(token).toBeDefined();
47+
expect(token!.text).toBe(expectedText);
48+
for (const scope of expectedScopes) {
49+
expect(token!.scopes).toContain(scope);
50+
}
51+
}
52+
53+
describe('inspectTokenScopesHandler - Location Based Tests', () => {
54+
let result: TokenizeResult;
55+
56+
beforeAll(async () => {
57+
result = await tokenizeContent(appJacContent, GRAMMAR_PATH, WASM_PATH);
58+
});
59+
60+
describe('Jac Keywords', () => {
61+
test('cl keyword at line 1', () => {
62+
// cl {
63+
expectToken(result, 1, 1, 3, 'cl', ['source.jac', 'storage.modifier.declaration.jac']);
64+
});
65+
66+
test('def keyword', () => {
67+
// def app() -> any {
68+
expectToken(result, 3, 5, 8, 'def', ['source.jac', 'meta.function.jac', 'storage.type.function.jac']);
69+
});
70+
71+
test('return keyword', () => {
72+
// return <div>
73+
expectToken(result, 5, 9, 15, 'return', ['source.jac', 'keyword.control.flow.jac']);
74+
});
75+
76+
test('lambda keyword', () => {
77+
// lambda e: any -> None { ... }
78+
expectToken(result, 8, 30, 36, 'lambda', ['source.jac', 'keyword.control.flow.jac']);
79+
});
80+
81+
test('with keyword', () => {
82+
// with entry{
83+
expectToken(result, 37, 1, 5, 'with', ['source.jac', 'storage.type.function.jac']);
84+
});
85+
86+
test('entry keyword', () => {
87+
// with entry{
88+
expectToken(result, 37, 6, 11, 'entry', ['source.jac', 'keyword.control.flow.jac']);
89+
});
90+
});
91+
92+
describe('Builtin Functions', () => {
93+
test('print builtin function', () => {
94+
// print("Hello, Jac!");
95+
expectToken(result, 38, 5, 10, 'print', ['source.jac', 'support.function.builtin.jac']);
96+
});
97+
});
98+
99+
describe('JSX HTML Tags (lowercase)', () => {
100+
test('div opening tag', () => {
101+
// <div>
102+
expectToken(result, 5, 17, 20, 'div', ['entity.name.tag.html.jsx.jac']);
103+
});
104+
105+
test('h1 opening tag', () => {
106+
// <h1>Hello, World!</h1>
107+
expectToken(result, 6, 14, 16, 'h1', ['entity.name.tag.html.jsx.jac']);
108+
});
109+
110+
test('p opening tag', () => {
111+
// <p>Count: {count}</p>
112+
expectToken(result, 7, 14, 15, 'p', ['entity.name.tag.html.jsx.jac']);
113+
});
114+
115+
test('button opening tag', () => {
116+
// <button onClick={...}>
117+
expectToken(result, 8, 14, 20, 'button', ['entity.name.tag.html.jsx.jac']);
118+
});
119+
});
120+
121+
describe('JSX Component Tags (PascalCase)', () => {
122+
test('ButtonComponent tag', () => {
123+
// <ButtonComponent label="Click Me" />
124+
expectToken(result, 11, 14, 29, 'ButtonComponent', ['support.class.component.jsx.jac']);
125+
});
126+
127+
test('NavLink opening tag', () => {
128+
// <NavLink to="/about">
129+
expectToken(result, 12, 14, 21, 'NavLink', ['support.class.component.jsx.jac']);
130+
});
131+
});
132+
133+
describe('JSX Attributes', () => {
134+
test('onClick attribute', () => {
135+
// <button onClick={...}>
136+
expectToken(result, 8, 21, 28, 'onClick', ['entity.other.attribute-name.jsx.jac']);
137+
});
138+
139+
test('label attribute', () => {
140+
// <ButtonComponent label="Click Me" />
141+
expectToken(result, 11, 30, 35, 'label', ['entity.other.attribute-name.jsx.jac']);
142+
});
143+
144+
test('to attribute', () => {
145+
// <NavLink to="/about">
146+
expectToken(result, 12, 22, 24, 'to', ['entity.other.attribute-name.jsx.jac']);
147+
});
148+
});
149+
150+
describe('JSX Attribute Strings', () => {
151+
test('string attribute value - Click Me', () => {
152+
// label="Click Me"
153+
expectToken(result, 11, 37, 45, 'Click Me', ['string.quoted.double.jac']);
154+
});
155+
156+
test('string attribute value - /about', () => {
157+
// to="/about"
158+
expectToken(result, 12, 26, 32, '/about', ['string.quoted.double.jac']);
159+
});
160+
});
161+
162+
describe('Keyword Escape Syntax', () => {
163+
test('<>esc keyword escape', () => {
164+
// a = <>esc;
165+
// esc is at columns 11-14 (1-based)
166+
const escToken = getTokenByLocation(result, 19, 11, 14);
167+
expect(escToken).toBeDefined();
168+
expect(escToken!.text).toBe('esc');
169+
expect(escToken!.scopes).toContain('variable.other.escaped.jac');
170+
});
171+
172+
test('<> punctuation for keyword escape', () => {
173+
// a = <>esc;
174+
// <> is at columns 9-11 (1-based)
175+
const punctToken = getTokenByLocation(result, 19, 9, 11);
176+
expect(punctToken).toBeDefined();
177+
expect(punctToken!.text).toBe('<>');
178+
expect(punctToken!.scopes).toContain('punctuation.definition.keyword-escape.jac');
179+
});
180+
});
181+
182+
describe('JSX Fragments', () => {
183+
test('fragment opening tag <>', () => {
184+
// <>
185+
// <div>First</div>
186+
// </>
187+
const fragmentOpen = getTokenByLocation(result, 22, 13, 15);
188+
expect(fragmentOpen).toBeDefined();
189+
expect(fragmentOpen!.text).toBe('<>');
190+
expect(fragmentOpen!.scopes).toContain('punctuation.definition.tag.jsx.jac');
191+
});
192+
193+
test('fragment closing tag </>', () => {
194+
const fragmentClose = getTokenByLocation(result, 29, 13, 16);
195+
expect(fragmentClose).toBeDefined();
196+
expect(fragmentClose!.text).toBe('</>');
197+
expect(fragmentClose!.scopes).toContain('punctuation.definition.tag.jsx.jac');
198+
});
199+
});
200+
201+
describe('Types', () => {
202+
test('any type annotation', () => {
203+
// def app() -> any {
204+
expectToken(result, 3, 18, 21, 'any', ['source.jac', 'support.type.jac']);
205+
});
206+
});
207+
208+
describe('Strings', () => {
209+
test('string literal - Hello, Jac!', () => {
210+
// print("Hello, Jac!");
211+
expectToken(result, 38, 12, 23, 'Hello, Jac!', ['string.quoted.single.jac']);
212+
});
213+
});
214+
});

0 commit comments

Comments
 (0)