Skip to content

Commit 81fa765

Browse files
authored
fix: recover syntax highlighting after incomplete new (#435)
1 parent e75943a commit 81fa765

6 files changed

Lines changed: 83 additions & 12 deletions

‎packages/e2e-integration/prepare.mjs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ if (!process.argv[2])
88
throw new Error('Pass the path to a disposable LVCE checkout')
99
const application = resolve(process.argv[2])
1010
const manifest = JSON.parse(
11-
await readFile(join(application, 'package.json'), 'utf8'),
11+
await readFile(join(application, 'package.json'), 'utf8')
1212
)
1313
if (manifest.name !== 'lvce-editor')
1414
throw new Error('Expected an LVCE application checkout')
@@ -30,7 +30,7 @@ try {
3030
for (const path of config.scripts) {
3131
await cp(
3232
join(here, 'scripts', path.split('/').at(-1)),
33-
join(application, path),
33+
join(application, path)
3434
)
3535
}
3636
// Exercise this repository's build in the pinned application runtime.

‎packages/e2e-integration/src/viewlet.editor-typescript-destructured-async-default-parameters.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,20 @@ const nextValue = 123
4646

4747
await expect(Locator('.Token.Text')).toHaveCount(0)
4848
await expect(Locator('.Token.Type', { hasText: 'EditorStatus' })).toHaveText(
49-
'EditorStatus',
49+
'EditorStatus'
5050
)
5151
await expect(Locator('.Token.Type', { hasText: 'StatusBarItem' })).toHaveText(
52-
'StatusBarItem',
52+
'StatusBarItem'
5353
)
5454
await expect(
55-
Locator('.Token.Type', { hasText: 'GetBuiltinStatusBarItemsOptions' }),
55+
Locator('.Token.Type', { hasText: 'GetBuiltinStatusBarItemsOptions' })
5656
).toHaveCount(2)
5757
await expect(Locator('.Token.Class', { hasText: 'Promise' })).toHaveText(
58-
'Promise',
58+
'Promise'
5959
)
6060
await expect(Locator('.Token.KeywordReturn')).toHaveCount(1)
6161
await expect(
62-
Locator('.Token.Function', { hasText: 'getEditorStatusBarItems' }),
62+
Locator('.Token.Function', { hasText: 'getEditorStatusBarItems' })
6363
).toHaveText('getEditorStatusBarItems')
6464
await expect(Locator('.Token.Numeric', { hasText: '123' })).toHaveText('123')
6565

‎packages/e2e-integration/src/viewlet.editor-typescript-generic-function-type-object-member.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export const createRpc = async (url = new URL('.tmp/cacheWorkerMain.js', import.
5050
)
5151
await expect(Locator('.Token.Punctuation', { hasText: '[' })).toHaveCount(1)
5252

53-
const genericLine = source.split('\n')[1]
53+
const genericLine = source.split('\n', 2)[1]
5454
const genericDefaultColumn = genericLine.indexOf('unknown') + 'unknown'.length
5555
await Editor.setCursor(1, genericDefaultColumn)
5656
await Editor.type('[]')

‎packages/e2e-integration/src/viewlet.editor-typescript-readonly-union-parameters.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,20 @@ export const test = async ({
2626
return process.children
2727
}
2828
const nextValue = 123
29-
`,
29+
`
3030
)
3131
await Settings.update({ 'editor.combineWhitespaceTokens': false })
3232
await Workspace.setUri(tmpDir)
3333
await Main.openUri(filePath)
3434

3535
await expect(
36-
Locator('.Token.TypePrimitive', { hasText: 'number' }),
36+
Locator('.Token.TypePrimitive', { hasText: 'number' })
3737
).toHaveText('number')
3838
await expect(Locator('.Token.Type', { hasText: 'ProcessInfo' })).toHaveText(
39-
'ProcessInfo',
39+
'ProcessInfo'
4040
)
4141
await expect(
42-
Locator('.Token.Type', { hasText: 'VisibleProcess' }),
42+
Locator('.Token.Type', { hasText: 'VisibleProcess' })
4343
).toHaveText('VisibleProcess')
4444
await expect(Locator('.Token.KeywordReturn')).toHaveCount(3)
4545
await expect(Locator('.Token.Numeric', { hasText: '123' })).toHaveText('123')

‎src/tokenizeTypeScript.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2975,6 +2975,8 @@ export const tokenizeLine = (line, lineState) => {
29752975
stack.push(state)
29762976
token = TokenType.Comment
29772977
state = State.InsideBlockComment
2978+
} else if ((next = part.match(RE_LINE_COMMENT))) {
2979+
token = TokenType.Comment
29782980
} else {
29792981
throw new Error('no')
29802982
}

‎test/incompleteNew.test.js‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import assert from 'node:assert/strict'
2+
import { test } from 'node:test'
3+
import {
4+
initialLineState,
5+
TokenMap,
6+
tokenizeLine,
7+
} from '../src/tokenizeTypeScript.js'
8+
9+
const tokenize = (source) => {
10+
let state = structuredClone(initialLineState)
11+
return source.split('\n').map((line) => {
12+
state = tokenizeLine(line, state)
13+
const tokens = []
14+
let offset = 0
15+
for (let index = 0; index < state.tokens.length; index += 2) {
16+
const type = TokenMap[state.tokens[index]]
17+
const length = state.tokens[index + 1]
18+
tokens.push([type, line.slice(offset, offset + length)])
19+
offset += length
20+
}
21+
assert.equal(offset, line.length)
22+
return tokens
23+
})
24+
}
25+
26+
test('recovers after a standalone new and a following line comment', () => {
27+
const lines = tokenize(
28+
`const instance = new \n// constructor to be added\nconst later = new Float32Array(9)`
29+
)
30+
31+
assert.ok(
32+
lines[0].some(([type, text]) => type === 'KeywordNew' && text === 'new')
33+
)
34+
assert.ok(
35+
lines[1].some(([type, text]) => type === 'Comment' && text.startsWith('//'))
36+
)
37+
assert.ok(
38+
lines[2].some(([type, text]) => type === 'VariableName' && text === 'later')
39+
)
40+
assert.ok(
41+
lines[2].some(([type, text]) => type === 'Class' && text === 'Float32Array')
42+
)
43+
})
44+
45+
test('recovers after an incomplete constructor name', () => {
46+
const lines = tokenize(
47+
`const instance = new Floa\n// finish the constructor\nconst later = 1`
48+
)
49+
50+
assert.ok(
51+
lines[0].some(([type, text]) => type === 'Class' && text === 'Floa')
52+
)
53+
assert.ok(
54+
lines[1].some(([type, text]) => type === 'Comment' && text.startsWith('//'))
55+
)
56+
assert.ok(
57+
lines[2].some(([type, text]) => type === 'VariableName' && text === 'later')
58+
)
59+
})
60+
61+
test('keeps constructor continuation after a comment following new', () => {
62+
const lines = tokenize(
63+
`const instance = new\n// constructor continues below\nFloat32Array(9)`
64+
)
65+
66+
assert.ok(
67+
lines[2].some(([type, text]) => type === 'Class' && text === 'Float32Array')
68+
)
69+
})

0 commit comments

Comments
 (0)