Skip to content

Commit 47bb06e

Browse files
authored
fix: highlight readonly tuple array parameters (#447)
1 parent 23dbf6c commit 47bb06e

4 files changed

Lines changed: 326 additions & 0 deletions

File tree

‎src/tokenizeTypeScript.js‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,7 @@ export const tokenizeLine = (line, lineState) => {
596596
lineState.isFunctionTypeAlias ||
597597
RE_FUNCTION_TYPE_ALIAS.test(line) ||
598598
isAmbientFunctionTypeDeclaration
599+
let isReadonlyTupleType = lineState.isReadonlyTupleType || false
599600
const arrowFunctionTypeOffsets = getArrowFunctionTypeOffsets(line)
600601
const genericTypeArgumentOffsets = getGenericTypeArgumentOffsets(line)
601602
const functionParameterTypeOffsets = getFunctionParameterTypeOffsets(line)
@@ -1277,11 +1278,33 @@ export const tokenizeLine = (line, lineState) => {
12771278
case State.InsideTypeExpression:
12781279
if ((next = part.match(RE_ROUND_CLOSE))) {
12791280
token = TokenType.Punctuation
1281+
if (
1282+
isReadonlyTupleType &&
1283+
stack.at(-1) === State.AfterTypeExpression
1284+
) {
1285+
stack.pop()
1286+
}
12801287
state = State.AfterTypeExpression
12811288
} else if ((next = part.match(RE_ANGLE_OPEN))) {
12821289
stack.push(State.InsideTypeExpression)
12831290
token = TokenType.Punctuation
12841291
state = State.InsideGeneric
1292+
} else if ((next = part.match(RE_KEYWORD_READONLY))) {
1293+
token = TokenType.KeywordModifier
1294+
isReadonlyTupleType = /^\s*\[/.test(part.slice(next[0].length))
1295+
state = State.InsideTypeExpression
1296+
} else if (
1297+
isReadonlyTupleType &&
1298+
(next = part.match(RE_TYPE_PRIMITIVE))
1299+
) {
1300+
token = TokenType.TypePrimitive
1301+
state = State.InsideTypeExpression
1302+
} else if (
1303+
isReadonlyTupleType &&
1304+
(next = part.match(RE_VARIABLE_NAME))
1305+
) {
1306+
token = TokenType.Type
1307+
state = State.InsideTypeExpression
12851308
} else if ((next = part.match(RE_VARIABLE_NAME))) {
12861309
token = TokenType.VariableName
12871310
state = State.AfterVariableName
@@ -1406,6 +1429,19 @@ export const tokenizeLine = (line, lineState) => {
14061429
if ((next = part.match(RE_SEMICOLON))) {
14071430
token = TokenType.Punctuation
14081431
state = State.TopLevelContent
1432+
} else if (isReadonlyTupleType && (next = part.match(RE_SQUARE_OPEN))) {
1433+
token = TokenType.Punctuation
1434+
state = State.AfterTypeExpression
1435+
} else if (
1436+
isReadonlyTupleType &&
1437+
(next = part.match(RE_SQUARE_CLOSE))
1438+
) {
1439+
token = TokenType.Punctuation
1440+
state = State.AfterTypeExpression
1441+
} else if (isReadonlyTupleType && (next = part.match(RE_COMMA))) {
1442+
token = TokenType.Punctuation
1443+
state = stack.pop() || State.TopLevelContent
1444+
isReadonlyTupleType = false
14091445
} else if ((next = part.match(RE_ARROW))) {
14101446
token = TokenType.Punctuation
14111447
if (!isAmbientFunctionTypeDeclaration) {
@@ -3561,6 +3597,7 @@ export const tokenizeLine = (line, lineState) => {
35613597
isArrowFunctionParameterObjectType,
35623598
isGenericArrowFunctionParameters,
35633599
isFunctionTypeAlias,
3600+
isReadonlyTupleType,
35643601
isTypeAssertion,
35653602
objectDepth,
35663603
parenthesisDepth,

‎test/baselines/test-worker-api.txt‎

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
KeywordImport
2+
Whitespace
3+
Keyword
4+
Whitespace
5+
Type
6+
Whitespace
7+
Punctuation
8+
NewLine
9+
Whitespace
10+
KeywordModifier
11+
Whitespace
12+
Function
13+
Punctuation
14+
Whitespace
15+
Punctuation
16+
NewLine
17+
Whitespace
18+
VariableName
19+
Punctuation
20+
Whitespace
21+
Type
22+
Punctuation
23+
NewLine
24+
Whitespace
25+
VariableName
26+
Punctuation
27+
Whitespace
28+
Type
29+
Punctuation
30+
NewLine
31+
Whitespace
32+
VariableName
33+
Punctuation
34+
Whitespace
35+
TypePrimitive
36+
Punctuation
37+
NewLine
38+
Whitespace
39+
VariableName
40+
Punctuation
41+
Whitespace
42+
Type
43+
Punctuation
44+
Type
45+
Punctuation
46+
TypePrimitive
47+
Punctuation
48+
Whitespace
49+
TypePrimitive
50+
Punctuation
51+
Punctuation
52+
Whitespace
53+
Punctuation
54+
Whitespace
55+
Type
56+
Punctuation
57+
Class
58+
Punctuation
59+
Whitespace
60+
Punctuation
61+
Whitespace
62+
KeywordModifier
63+
Whitespace
64+
Punctuation
65+
KeywordModifier
66+
Whitespace
67+
Punctuation
68+
TypePrimitive
69+
Punctuation
70+
Whitespace
71+
TypePrimitive
72+
Punctuation
73+
Punctuation
74+
Punctuation
75+
Punctuation
76+
Punctuation
77+
NewLine
78+
Whitespace
79+
VariableName
80+
Punctuation
81+
Whitespace
82+
TypePrimitive
83+
Punctuation
84+
NewLine
85+
Whitespace
86+
VariableName
87+
Punctuation
88+
Whitespace
89+
Type
90+
Punctuation
91+
Type
92+
Punctuation
93+
Punctuation
94+
NewLine
95+
Whitespace
96+
Punctuation
97+
Whitespace
98+
Punctuation
99+
Whitespace
100+
Class
101+
Punctuation
102+
Type
103+
Punctuation
104+
NewLine
105+
Whitespace
106+
KeywordModifier
107+
Whitespace
108+
Function
109+
Punctuation
110+
Whitespace
111+
Punctuation
112+
VariableName
113+
Punctuation
114+
Whitespace
115+
TypePrimitive
116+
Punctuation
117+
Whitespace
118+
VariableName
119+
Punctuation
120+
Whitespace
121+
Type
122+
Punctuation
123+
Whitespace
124+
Punctuation
125+
Whitespace
126+
Class
127+
Punctuation
128+
TypePrimitive
129+
Punctuation
130+
Punctuation
131+
NewLine
132+
Punctuation
133+
NewLine
134+
NewLine
135+
KeywordImport
136+
Whitespace
137+
Keyword
138+
Whitespace
139+
Type
140+
Whitespace
141+
Punctuation
142+
NewLine
143+
Whitespace
144+
KeywordModifier
145+
Whitespace
146+
VariableName
147+
Punctuation
148+
Whitespace
149+
Class
150+
Punctuation
151+
NewLine
152+
Whitespace
153+
KeywordModifier
154+
Whitespace
155+
VariableName
156+
Punctuation
157+
Whitespace
158+
TypePrimitive
159+
Punctuation
160+
NewLine
161+
Punctuation
162+
NewLine
163+
NewLine
164+
KeywordImport
165+
Whitespace
166+
Keyword
167+
Whitespace
168+
Type
169+
Punctuation
170+
Type
171+
Punctuation
172+
Whitespace
173+
Type
174+
Whitespace
175+
KeywordModifier
176+
Whitespace
177+
Type
178+
Punctuation
179+
Whitespace
180+
Punctuation
181+
NewLine
182+
Whitespace
183+
KeywordModifier
184+
Whitespace
185+
VariableName
186+
Punctuation
187+
Whitespace
188+
Punctuation
189+
NewLine
190+
Whitespace
191+
KeywordModifier
192+
Whitespace
193+
VariableName
194+
Punctuation
195+
Whitespace
196+
Type
197+
Punctuation
198+
NewLine
199+
Whitespace
200+
KeywordModifier
201+
Whitespace
202+
VariableName
203+
Punctuation
204+
Whitespace
205+
Type
206+
Punctuation
207+
NewLine
208+
Whitespace
209+
Punctuation
210+
Punctuation
211+
NewLine
212+
Punctuation
213+
NewLine

‎test/cases/test-worker-api.ts‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export interface CacheWorkerPageObject {
2+
readonly setCacheStorageItem: (
3+
request: CacheRequest,
4+
value: BodyInit,
5+
cacheName?: string,
6+
headers?: Readonly<Record<string, string>> | Readonly<Headers> | readonly (readonly [string, string])[],
7+
bucketName?: string,
8+
bucketOptions?: Readonly<StorageBucketOptions>,
9+
) => Promise<CacheStorageWriteResult>
10+
readonly writeFile: (name: string, content: FileSystemWriteChunkType) => Promise<void>;
11+
}
12+
13+
export interface DroppedFileHandle {
14+
readonly file: File;
15+
readonly id: number;
16+
}
17+
18+
export interface ToolCallItemBase<TArguments, TName extends string> {
19+
readonly toolCall: {
20+
readonly arguments: TArguments;
21+
readonly name: TName;
22+
};
23+
}

‎test/testWorkerApi.test.js‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import assert from 'node:assert/strict'
2+
import { readFile } from 'node:fs/promises'
3+
import { test } from 'node:test'
4+
import * as TypeScript from '../src/tokenizeTypeScript.js'
5+
import * as TypeScriptReact from '../src/tokenizeTypeScriptReact.js'
6+
7+
const source = await readFile(
8+
new URL('./cases/test-worker-api.ts', import.meta.url),
9+
'utf8'
10+
)
11+
12+
for (const [language, tokenizer] of [
13+
['TypeScript', TypeScript],
14+
['TSX', TypeScriptReact],
15+
]) {
16+
test(`${language} highlights test-worker api declarations after readonly tuple parameters`, () => {
17+
let state = structuredClone(tokenizer.initialLineState)
18+
const tokens = []
19+
20+
for (const line of source.split('\n')) {
21+
state = tokenizer.tokenizeLine(line, state)
22+
let offset = 0
23+
for (let index = 0; index < state.tokens.length; index += 2) {
24+
const length = state.tokens[index + 1]
25+
tokens.push([
26+
tokenizer.TokenMap[state.tokens[index]],
27+
line.slice(offset, offset + length),
28+
])
29+
offset += length
30+
}
31+
assert.equal(offset, line.length)
32+
}
33+
34+
assert.deepEqual(
35+
tokens.filter(([type]) => type === 'Text'),
36+
[]
37+
)
38+
for (const [type, text] of [
39+
['Function', 'setCacheStorageItem'],
40+
['Function', 'writeFile'],
41+
['Type', 'DroppedFileHandle'],
42+
['Type', 'ToolCallItemBase'],
43+
['Type', 'TArguments'],
44+
['Type', 'TName'],
45+
['TypePrimitive', 'string'],
46+
]) {
47+
assert.ok(
48+
tokens.some((token) => token[0] === type && token[1] === text),
49+
`${language} should classify ${text} as ${type}`
50+
)
51+
}
52+
})
53+
}

0 commit comments

Comments
 (0)