coc-vscode-loader 1.2.9 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/converter/README.md +66 -11
- package/converter/package-lock.json +1363 -146
- package/converter/package.json +9 -4
- package/converter/scripts/check-tests.ts +58 -0
- package/converter/scripts/smoke-test.ts +234 -0
- package/converter/src/cli.ts +4 -1
- package/converter/src/convert.test.ts +292 -0
- package/converter/src/convert.ts +5 -3
- package/converter/src/presets.test.ts +37 -0
- package/converter/src/registry-validation.test.ts +127 -0
- package/converter/src/scanner.test.ts +67 -0
- package/converter/src/scanner.ts +1 -1
- package/converter/src/steps/bridge.test.ts +72 -0
- package/converter/src/steps/language-client.test.ts +131 -0
- package/converter/src/steps/language-client.ts +1 -1
- package/converter/src/steps/mark-unsupported.test.ts +109 -0
- package/converter/src/steps/snippets.test.ts +114 -0
- package/converter/src/steps/snippets.ts +12 -3
- package/converter/src/steps/source.test.ts +117 -0
- package/converter/src/steps/source.ts +2 -4
- package/converter/src/transforms/class-to-factory.test.ts +60 -0
- package/converter/src/transforms/enum-offset.test.ts +27 -0
- package/converter/src/transforms/import-mapping.test.ts +227 -0
- package/converter/src/transforms/import-mapping.ts +32 -11
- package/converter/src/transforms/language-client.test.ts +48 -0
- package/converter/src/transforms/provider-register.test.ts +65 -0
- package/converter/src/transforms/provider-register.ts +1 -1
- package/converter/src/transforms/strip-volar.test.ts +35 -0
- package/converter/src/types.ts +2 -0
- package/converter/vitest.config.ts +8 -0
- package/lib/index.js +99 -50
- package/package.json +1 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
describe('presets', () => {
|
|
4
|
+
it('getPreset returns ts-bridge preset', async () => {
|
|
5
|
+
const { getPreset } = await import('./presets.js')
|
|
6
|
+
const preset = getPreset('ts-bridge')
|
|
7
|
+
expect(preset).toBeTruthy()
|
|
8
|
+
expect(preset!.name).toBe('ts-bridge')
|
|
9
|
+
expect(preset!.notification).toBe('tsserver/request')
|
|
10
|
+
expect(preset!.extraDeps).toContain('typescript')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('getPreset returns undefined for unknown preset', async () => {
|
|
14
|
+
const { getPreset } = await import('./presets.js')
|
|
15
|
+
expect(getPreset('nonexistent')).toBeUndefined()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('getActivePresets returns ts-bridge when hasTsBridge is true', async () => {
|
|
19
|
+
const { getActivePresets } = await import('./presets.js')
|
|
20
|
+
const presets = getActivePresets(true, {})
|
|
21
|
+
expect(presets).toHaveLength(1)
|
|
22
|
+
expect(presets[0].name).toBe('ts-bridge')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('getActivePresets returns empty when hasTsBridge is false', async () => {
|
|
26
|
+
const { getActivePresets } = await import('./presets.js')
|
|
27
|
+
expect(getActivePresets(false, {})).toEqual([])
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('generateBridgeCode joins preset codes', async () => {
|
|
31
|
+
const { generateBridgeCode, getPreset } = await import('./presets.js')
|
|
32
|
+
const preset = getPreset('ts-bridge')!
|
|
33
|
+
const code = generateBridgeCode([preset, preset])
|
|
34
|
+
expect(code).toContain('tsserver')
|
|
35
|
+
expect(code).toContain('\n\n')
|
|
36
|
+
})
|
|
37
|
+
})
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from 'vitest'
|
|
2
|
+
import * as fs from 'fs'
|
|
3
|
+
import * as path from 'path'
|
|
4
|
+
|
|
5
|
+
interface RegistryEntry {
|
|
6
|
+
name: string
|
|
7
|
+
displayName: string
|
|
8
|
+
description: string
|
|
9
|
+
type: string
|
|
10
|
+
source: { type: string; repo?: string; package?: string; subdir?: string }
|
|
11
|
+
url: string
|
|
12
|
+
languages: string[]
|
|
13
|
+
categories: string[]
|
|
14
|
+
convert: any[]
|
|
15
|
+
minPluginVersion?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('registry.json validation', () => {
|
|
19
|
+
const registryPath = path.resolve(__dirname, '../../coc-vscode-registry/registry.json')
|
|
20
|
+
let entries: RegistryEntry[]
|
|
21
|
+
|
|
22
|
+
beforeAll(() => {
|
|
23
|
+
const content = fs.readFileSync(registryPath, 'utf-8')
|
|
24
|
+
entries = JSON.parse(content) as RegistryEntry[]
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('is valid JSON and non-empty', () => {
|
|
28
|
+
expect(entries.length).toBeGreaterThan(0)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('every entry has required fields', () => {
|
|
32
|
+
for (const e of entries) {
|
|
33
|
+
expect(e.name).toBeTruthy()
|
|
34
|
+
expect(e.displayName).toBeTruthy()
|
|
35
|
+
expect(e.description).toBeTruthy()
|
|
36
|
+
expect(e.type).toBeTruthy()
|
|
37
|
+
expect(e.languages).toBeInstanceOf(Array)
|
|
38
|
+
expect(e.categories).toBeInstanceOf(Array)
|
|
39
|
+
expect(e.convert).toBeInstanceOf(Array)
|
|
40
|
+
expect(e.convert.length).toBeGreaterThan(0)
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('every entry has valid type', () => {
|
|
45
|
+
const validTypes = ['ts-bridge', 'pure-lsp', 'direct-api', 'snippets', 'lsp']
|
|
46
|
+
for (const e of entries) {
|
|
47
|
+
expect(validTypes).toContain(e.type)
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('every entry has valid source', () => {
|
|
52
|
+
const validSourceTypes = ['github', 'npm']
|
|
53
|
+
for (const e of entries) {
|
|
54
|
+
expect(validSourceTypes).toContain(e.source.type)
|
|
55
|
+
if (e.source.type === 'github') {
|
|
56
|
+
expect(e.source.repo).toBeTruthy()
|
|
57
|
+
} else if (e.source.type === 'npm') {
|
|
58
|
+
expect(e.source.package).toBeTruthy()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('every convert step has a known type', () => {
|
|
64
|
+
const knownStepTypes = ['source', 'language-client', 'bridge', 'snippets', 'mark-unsupported']
|
|
65
|
+
for (const e of entries) {
|
|
66
|
+
for (const step of e.convert) {
|
|
67
|
+
expect(knownStepTypes).toContain(step.type)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('language-server steps have valid server config', () => {
|
|
73
|
+
for (const e of entries) {
|
|
74
|
+
for (const step of e.convert) {
|
|
75
|
+
if (step.type === 'language-client') {
|
|
76
|
+
expect(step.server).toBeTruthy()
|
|
77
|
+
expect(['module', 'binary']).toContain(step.server.kind)
|
|
78
|
+
expect(step.server.package).toBeTruthy()
|
|
79
|
+
expect(step.languages).toBeInstanceOf(Array)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('snippets entries have type "snippets"', () => {
|
|
86
|
+
const snippetsEntries = entries.filter(e => e.convert.some(s => s.type === 'snippets'))
|
|
87
|
+
for (const e of snippetsEntries) {
|
|
88
|
+
expect(e.type).toBe('snippets')
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('no duplicate names', () => {
|
|
93
|
+
const names = entries.map(e => e.name)
|
|
94
|
+
const unique = new Set(names)
|
|
95
|
+
expect(unique.size).toBe(names.length)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('descriptions are non-empty', () => {
|
|
99
|
+
for (const e of entries) {
|
|
100
|
+
expect(e.description.length).toBeGreaterThan(0)
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('every entry has at least one language', () => {
|
|
105
|
+
for (const e of entries) {
|
|
106
|
+
expect(e.languages.length).toBeGreaterThan(0)
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('minPluginVersion is valid semver if present', () => {
|
|
111
|
+
const semverRe = /^\d+\.\d+\.\d+/
|
|
112
|
+
for (const e of entries) {
|
|
113
|
+
if (e.minPluginVersion) {
|
|
114
|
+
expect(e.minPluginVersion).toMatch(semverRe)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('bridge entries have bridge step with preset', () => {
|
|
120
|
+
const bridgeEntries = entries.filter(e => e.type === 'ts-bridge')
|
|
121
|
+
for (const e of bridgeEntries) {
|
|
122
|
+
const bridgeStep = e.convert.find(s => s.type === 'bridge')
|
|
123
|
+
expect(bridgeStep).toBeTruthy()
|
|
124
|
+
expect(bridgeStep!.preset).toBeTruthy()
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import * as fs from 'fs'
|
|
4
|
+
import * as os from 'os'
|
|
5
|
+
|
|
6
|
+
describe('scanner', () => {
|
|
7
|
+
it('returns empty result for missing directory', async () => {
|
|
8
|
+
const { scan } = await import('./scanner.js')
|
|
9
|
+
const result = scan('/nonexistent/path')
|
|
10
|
+
expect(result.files).toHaveLength(0)
|
|
11
|
+
expect(result.summary).toContain('no source directory')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('detects vscode imports in .ts files', async () => {
|
|
15
|
+
const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'scanner-test-'))
|
|
16
|
+
fs.writeFileSync(path.join(tmpdir, 'ext.ts'), "import * as vscode from 'vscode'")
|
|
17
|
+
const { scan } = await import('./scanner.js')
|
|
18
|
+
const result = scan(tmpdir)
|
|
19
|
+
expect(result.files).toHaveLength(1)
|
|
20
|
+
expect(result.files[0].apis).toContain('vscode')
|
|
21
|
+
expect(result.summary).toContain('1 files')
|
|
22
|
+
fs.rmSync(tmpdir, { recursive: true, force: true })
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('detects require("vscode") in .ts files', async () => {
|
|
26
|
+
const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'scanner-test-'))
|
|
27
|
+
fs.writeFileSync(path.join(tmpdir, 'ext.ts'), "const vscode = require('vscode')")
|
|
28
|
+
const { scan } = await import('./scanner.js')
|
|
29
|
+
const result = scan(tmpdir)
|
|
30
|
+
expect(result.files).toHaveLength(1)
|
|
31
|
+
expect(result.files[0].apis).toContain('vscode')
|
|
32
|
+
fs.rmSync(tmpdir, { recursive: true, force: true })
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('detects vscode imports in .js files', async () => {
|
|
36
|
+
const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'scanner-test-'))
|
|
37
|
+
fs.writeFileSync(path.join(tmpdir, 'ext.js'), "const vscode = require('vscode')")
|
|
38
|
+
const { scan } = await import('./scanner.js')
|
|
39
|
+
const result = scan(tmpdir)
|
|
40
|
+
expect(result.files).toHaveLength(1)
|
|
41
|
+
expect(result.files[0].apis).toContain('vscode')
|
|
42
|
+
fs.rmSync(tmpdir, { recursive: true, force: true })
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('recurses into subdirectories', async () => {
|
|
46
|
+
const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'scanner-test-'))
|
|
47
|
+
fs.mkdirSync(path.join(tmpdir, 'sub'))
|
|
48
|
+
fs.writeFileSync(path.join(tmpdir, 'sub', 'ext.ts'), "import * as vscode from 'vscode'")
|
|
49
|
+
const { scan } = await import('./scanner.js')
|
|
50
|
+
const result = scan(tmpdir)
|
|
51
|
+
expect(result.files).toHaveLength(1)
|
|
52
|
+
expect(result.files[0].path).toContain('sub')
|
|
53
|
+
fs.rmSync(tmpdir, { recursive: true, force: true })
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('skips node_modules and dot dirs', async () => {
|
|
57
|
+
const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'scanner-test-'))
|
|
58
|
+
fs.mkdirSync(path.join(tmpdir, 'node_modules'), { recursive: true })
|
|
59
|
+
fs.writeFileSync(path.join(tmpdir, 'node_modules', 'ext.ts'), "import * as vscode from 'vscode'")
|
|
60
|
+
fs.mkdirSync(path.join(tmpdir, '.git'), { recursive: true })
|
|
61
|
+
fs.writeFileSync(path.join(tmpdir, '.git', 'ext.ts'), "import * as vscode from 'vscode'")
|
|
62
|
+
const { scan } = await import('./scanner.js')
|
|
63
|
+
const result = scan(tmpdir)
|
|
64
|
+
expect(result.files).toHaveLength(0)
|
|
65
|
+
fs.rmSync(tmpdir, { recursive: true, force: true })
|
|
66
|
+
})
|
|
67
|
+
})
|
package/converter/src/scanner.ts
CHANGED
|
@@ -16,7 +16,7 @@ export function scan(dir: string): ScanResult {
|
|
|
16
16
|
if (!fs.existsSync(dir)) {
|
|
17
17
|
return { files, summary: 'no source directory found' }
|
|
18
18
|
}
|
|
19
|
-
const tsFiles = walk(dir).filter(f => f.endsWith('.ts') || f.endsWith('.tsx'))
|
|
19
|
+
const tsFiles = walk(dir).filter(f => f.endsWith('.ts') || f.endsWith('.tsx') || f.endsWith('.js'))
|
|
20
20
|
|
|
21
21
|
for (const filePath of tsFiles) {
|
|
22
22
|
const content = fs.readFileSync(filePath, 'utf-8')
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
describe('bridge step', () => {
|
|
4
|
+
it('throws on unknown preset', async () => {
|
|
5
|
+
const { bridgeGenerator } = await import('./bridge.js')
|
|
6
|
+
expect(() => {
|
|
7
|
+
bridgeGenerator.generate(
|
|
8
|
+
{ input: '/fake', output: '/fake/out', origPkg: {}, project: null as any, presets: {} },
|
|
9
|
+
{ type: 'bridge', preset: 'nonexistent' },
|
|
10
|
+
)
|
|
11
|
+
}).toThrow('Unknown bridge preset')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('throws on empty type without preset', async () => {
|
|
15
|
+
const { bridgeGenerator } = await import('./bridge.js')
|
|
16
|
+
expect(() => {
|
|
17
|
+
bridgeGenerator.generate(
|
|
18
|
+
{ input: '/fake', output: '/fake/out', origPkg: {}, project: null as any },
|
|
19
|
+
{ type: 'bridge', options: {} },
|
|
20
|
+
)
|
|
21
|
+
}).toThrow('Unknown bridge type')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('generates tsserver-forward bridge with preset', async () => {
|
|
25
|
+
const { bridgeGenerator } = await import('./bridge.js')
|
|
26
|
+
const result = bridgeGenerator.generate(
|
|
27
|
+
{
|
|
28
|
+
input: '/fake',
|
|
29
|
+
output: '/fake/out',
|
|
30
|
+
origPkg: { name: 'test', dependencies: { typescript: '^5.0.0' } },
|
|
31
|
+
project: null as any,
|
|
32
|
+
presets: {
|
|
33
|
+
'ts-bridge': {
|
|
34
|
+
type: 'tsserver-forward',
|
|
35
|
+
options: { extensions: ['vue'], services: ['typescript'] },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{ type: 'bridge', preset: 'ts-bridge' },
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
expect(result.generatedFiles).toHaveLength(1)
|
|
43
|
+
expect(result.generatedFiles[0].path).toBe('src/bridge.ts')
|
|
44
|
+
expect(result.generatedFiles[0].content).toContain('registerBridge')
|
|
45
|
+
expect(result.generatedFiles[0].content).toContain('tsserver/request')
|
|
46
|
+
|
|
47
|
+
expect(result.keepDeps).toHaveProperty('typescript')
|
|
48
|
+
expect(result.keepDeps.typescript).toBe('^5.0.0')
|
|
49
|
+
|
|
50
|
+
expect(result.codeInjections).toBeDefined()
|
|
51
|
+
expect(result.codeInjections!.length).toBeGreaterThan(0)
|
|
52
|
+
expect(result.codeInjections![0].target).toBe('src/index.ts')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('includes verbose logging when enabled', async () => {
|
|
56
|
+
const { bridgeGenerator } = await import('./bridge.js')
|
|
57
|
+
const result = bridgeGenerator.generate(
|
|
58
|
+
{
|
|
59
|
+
input: '/fake',
|
|
60
|
+
output: '/fake/out',
|
|
61
|
+
origPkg: { name: 'test', dependencies: { typescript: '*' } },
|
|
62
|
+
project: null as any,
|
|
63
|
+
presets: { 'ts-bridge': { type: 'tsserver-forward' } },
|
|
64
|
+
},
|
|
65
|
+
{ type: 'bridge', preset: 'ts-bridge', options: {}, verbose: true },
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
const code = result.generatedFiles[0].content
|
|
69
|
+
expect(code).toContain('[bridge] registerBridge called')
|
|
70
|
+
expect(code).toContain('[bridge] client ready')
|
|
71
|
+
})
|
|
72
|
+
})
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
describe('language-client step', () => {
|
|
4
|
+
it('generates valid index.ts for module-kind server', async () => {
|
|
5
|
+
const { languageClientGenerator } = await import('./language-client.js')
|
|
6
|
+
const result = languageClientGenerator.generate(
|
|
7
|
+
{
|
|
8
|
+
input: '/fake',
|
|
9
|
+
output: '/fake/out',
|
|
10
|
+
origPkg: { name: 'test-ls', description: 'Test LS' },
|
|
11
|
+
project: null as any,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
type: 'language-client',
|
|
15
|
+
server: { kind: 'module', package: 'test-language-server', entry: 'main' },
|
|
16
|
+
languages: ['test'],
|
|
17
|
+
},
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
expect(result.entryPoint).toBe('src/index.ts')
|
|
21
|
+
expect(result.generatedFiles).toHaveLength(1)
|
|
22
|
+
expect(result.generatedFiles[0].path).toBe('src/index.ts')
|
|
23
|
+
|
|
24
|
+
const code = result.generatedFiles[0].content
|
|
25
|
+
expect(code).toContain('LanguageClient')
|
|
26
|
+
expect(code).toContain("from 'coc.nvim'")
|
|
27
|
+
expect(code).toContain('test-language-server')
|
|
28
|
+
expect(code).toContain('activate')
|
|
29
|
+
expect(code).toContain('context.subscriptions.push')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('generates binary server download code for binary-kind server', async () => {
|
|
33
|
+
const { languageClientGenerator } = await import('./language-client.js')
|
|
34
|
+
const result = languageClientGenerator.generate(
|
|
35
|
+
{
|
|
36
|
+
input: '/fake',
|
|
37
|
+
output: '/fake/out',
|
|
38
|
+
origPkg: { name: 'binary-ls', description: 'Binary LS' },
|
|
39
|
+
project: null as any,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: 'language-client',
|
|
43
|
+
server: {
|
|
44
|
+
kind: 'binary',
|
|
45
|
+
package: 'my-server',
|
|
46
|
+
binary: { repo: 'user/repo', asset: 'server-{{version}}-{{platform}}-{{arch}}.tar.gz', binaryPath: 'bin/server' },
|
|
47
|
+
},
|
|
48
|
+
languages: ['test'],
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
// Binary config is stored in result.serverBinary, not embedded in code
|
|
53
|
+
expect(result.serverBinary).toBeTruthy()
|
|
54
|
+
expect(result.serverBinary!.repo).toBe('user/repo')
|
|
55
|
+
|
|
56
|
+
const code = result.generatedFiles[0].content
|
|
57
|
+
expect(code).toContain('LanguageClient')
|
|
58
|
+
expect(code).toContain("from 'coc.nvim'")
|
|
59
|
+
expect(code).toContain('require.resolve')
|
|
60
|
+
expect(code).toContain("'bin/server'")
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('generates stdio transport for binary kind', async () => {
|
|
64
|
+
const { languageClientGenerator } = await import('./language-client.js')
|
|
65
|
+
const result = languageClientGenerator.generate(
|
|
66
|
+
{
|
|
67
|
+
input: '/fake',
|
|
68
|
+
output: '/fake/out',
|
|
69
|
+
origPkg: { name: 'stdio-ls' },
|
|
70
|
+
project: null as any,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: 'language-client',
|
|
74
|
+
server: { kind: 'binary', package: 'bin-ls', binary: { repo: 'user/repo', asset: 'bin-{{version}}.tar.gz' } },
|
|
75
|
+
languages: ['test'],
|
|
76
|
+
transport: 'stdio',
|
|
77
|
+
},
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
const code = result.generatedFiles[0].content
|
|
81
|
+
// Binary kind with stdio uses command: serverPath (Executable options), not module+TransportKind
|
|
82
|
+
// TransportKind is only used for module kind where { module, transport } is set
|
|
83
|
+
expect(code).toContain("command: serverPath")
|
|
84
|
+
expect(code).not.toContain('TransportKind.ipc')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('includes initializationOptions when configured', async () => {
|
|
88
|
+
const { languageClientGenerator } = await import('./language-client.js')
|
|
89
|
+
const result = languageClientGenerator.generate(
|
|
90
|
+
{
|
|
91
|
+
input: '/fake',
|
|
92
|
+
output: '/fake/out',
|
|
93
|
+
origPkg: { name: 'init-ls' },
|
|
94
|
+
project: null as any,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
type: 'language-client',
|
|
98
|
+
server: { kind: 'module', package: 'init-ls-server' },
|
|
99
|
+
languages: ['test'],
|
|
100
|
+
initializationOptions: '{ typescript: { tsdk: serverPath } }',
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
const code = result.generatedFiles[0].content
|
|
105
|
+
expect(code).toContain('initializationOptions')
|
|
106
|
+
expect(code).toContain('tsdk: serverPath')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('uses custom id for LanguageClient name', async () => {
|
|
110
|
+
const { languageClientGenerator } = await import('./language-client.js')
|
|
111
|
+
const result = languageClientGenerator.generate(
|
|
112
|
+
{
|
|
113
|
+
input: '/fake',
|
|
114
|
+
output: '/fake/out',
|
|
115
|
+
origPkg: { name: 'default-name' },
|
|
116
|
+
project: null as any,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
type: 'language-client',
|
|
120
|
+
id: 'my-custom-id',
|
|
121
|
+
server: { kind: 'module', package: 'some-server' },
|
|
122
|
+
languages: ['test'],
|
|
123
|
+
},
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
const code = result.generatedFiles[0].content
|
|
127
|
+
expect(code).toContain("'my-custom-id'")
|
|
128
|
+
// The description/command still use origPkg name, only the id is custom
|
|
129
|
+
expect(code).toContain("'default-name'")
|
|
130
|
+
})
|
|
131
|
+
})
|
|
@@ -93,7 +93,7 @@ import * as path from 'path'
|
|
|
93
93
|
export async function activate(context: ExtensionContext): Promise<void> {
|
|
94
94
|
try {
|
|
95
95
|
${ls.verbose ? ` console.log('[${escapeStr(id)}] activate() called')\n` : ''}${serverPathCode}
|
|
96
|
-
if (!serverPath) {
|
|
96
|
+
if (!serverPath && !_mainEntry) {
|
|
97
97
|
${ls.verbose ? ` console.log('[${escapeStr(id)}] serverPath undefined')\n` : ''}\
|
|
98
98
|
window.showErrorMessage('Cannot find language server.')
|
|
99
99
|
return
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import * as os from 'os'
|
|
4
|
+
import * as fs from 'fs'
|
|
5
|
+
|
|
6
|
+
describe('mark-unsupported step', () => {
|
|
7
|
+
let tmpdir: string
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'mark-unsup-'))
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
fs.rmSync(tmpdir, { recursive: true, force: true })
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
function writeSrc(rel: string, content: string) {
|
|
18
|
+
const fp = path.join(tmpdir, 'src', rel)
|
|
19
|
+
fs.mkdirSync(path.dirname(fp), { recursive: true })
|
|
20
|
+
fs.writeFileSync(fp, content)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
it('comments out createTextEditorDecorationType calls', async () => {
|
|
24
|
+
writeSrc('ext.ts', 'editor.createTextEditorDecorationType({})')
|
|
25
|
+
const { markUnsupportedGenerator } = await import('./mark-unsupported.js')
|
|
26
|
+
markUnsupportedGenerator.generate(
|
|
27
|
+
{ input: tmpdir, output: tmpdir, origPkg: {}, project: null as any },
|
|
28
|
+
{ type: 'mark-unsupported', features: ['decoration'] },
|
|
29
|
+
)
|
|
30
|
+
const content = fs.readFileSync(path.join(tmpdir, 'src', 'ext.ts'), 'utf-8')
|
|
31
|
+
expect(content).toContain('TODO: Decoration API is not supported')
|
|
32
|
+
expect(content).toContain('void 0')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('comments out setDecorations calls', async () => {
|
|
36
|
+
writeSrc('ext.ts', 'editor.setDecorations(dec, ranges)')
|
|
37
|
+
const { markUnsupportedGenerator } = await import('./mark-unsupported.js')
|
|
38
|
+
markUnsupportedGenerator.generate(
|
|
39
|
+
{ input: tmpdir, output: tmpdir, origPkg: {}, project: null as any },
|
|
40
|
+
{ type: 'mark-unsupported', features: ['decoration'] },
|
|
41
|
+
)
|
|
42
|
+
const content = fs.readFileSync(path.join(tmpdir, 'src', 'ext.ts'), 'utf-8')
|
|
43
|
+
expect(content).toContain('TODO: Decoration API is not supported')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('comments out createWebviewPanel calls', async () => {
|
|
47
|
+
writeSrc('ext.ts', 'const panel = window.createWebviewPanel("view", "title", viewColumn)')
|
|
48
|
+
const { markUnsupportedGenerator } = await import('./mark-unsupported.js')
|
|
49
|
+
markUnsupportedGenerator.generate(
|
|
50
|
+
{ input: tmpdir, output: tmpdir, origPkg: {}, project: null as any },
|
|
51
|
+
{ type: 'mark-unsupported', features: ['webview'] },
|
|
52
|
+
)
|
|
53
|
+
const content = fs.readFileSync(path.join(tmpdir, 'src', 'ext.ts'), 'utf-8')
|
|
54
|
+
expect(content).toContain('TODO: Webview API is not supported')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('comments out registerTreeDataProvider calls', async () => {
|
|
58
|
+
writeSrc('ext.ts', 'window.registerTreeDataProvider("view", provider)')
|
|
59
|
+
const { markUnsupportedGenerator } = await import('./mark-unsupported.js')
|
|
60
|
+
markUnsupportedGenerator.generate(
|
|
61
|
+
{ input: tmpdir, output: tmpdir, origPkg: {}, project: null as any },
|
|
62
|
+
{ type: 'mark-unsupported', features: ['tree-data-provider'] },
|
|
63
|
+
)
|
|
64
|
+
const content = fs.readFileSync(path.join(tmpdir, 'src', 'ext.ts'), 'utf-8')
|
|
65
|
+
expect(content).toContain('TODO: Tree data provider is not supported')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('comments out env.openExternal calls', async () => {
|
|
69
|
+
writeSrc('ext.ts', 'env.openExternal(url)')
|
|
70
|
+
const { markUnsupportedGenerator } = await import('./mark-unsupported.js')
|
|
71
|
+
markUnsupportedGenerator.generate(
|
|
72
|
+
{ input: tmpdir, output: tmpdir, origPkg: {}, project: null as any },
|
|
73
|
+
{ type: 'mark-unsupported', features: ['open-external'] },
|
|
74
|
+
)
|
|
75
|
+
const content = fs.readFileSync(path.join(tmpdir, 'src', 'ext.ts'), 'utf-8')
|
|
76
|
+
expect(content).toContain('TODO: env.openExternal has no equivalent')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('handles unknown feature gracefully', async () => {
|
|
80
|
+
writeSrc('ext.ts', 'const x = 1')
|
|
81
|
+
const { markUnsupportedGenerator } = await import('./mark-unsupported.js')
|
|
82
|
+
const result = markUnsupportedGenerator.generate(
|
|
83
|
+
{ input: tmpdir, output: tmpdir, origPkg: {}, project: null as any },
|
|
84
|
+
{ type: 'mark-unsupported', features: ['nonexistent'] },
|
|
85
|
+
)
|
|
86
|
+
expect(result.generatedFiles).toEqual([])
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('handles missing src directory', async () => {
|
|
90
|
+
const outdir = tmpdir + '/out'
|
|
91
|
+
const { markUnsupportedGenerator } = await import('./mark-unsupported.js')
|
|
92
|
+
const result = markUnsupportedGenerator.generate(
|
|
93
|
+
{ input: tmpdir, output: outdir, origPkg: {}, project: null as any },
|
|
94
|
+
{ type: 'mark-unsupported', features: ['decoration'] },
|
|
95
|
+
)
|
|
96
|
+
expect(result.generatedFiles).toEqual([])
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('only processes .ts files', async () => {
|
|
100
|
+
writeSrc('ext.js', 'editor.setDecorations(dec, ranges)')
|
|
101
|
+
const { markUnsupportedGenerator } = await import('./mark-unsupported.js')
|
|
102
|
+
markUnsupportedGenerator.generate(
|
|
103
|
+
{ input: tmpdir, output: tmpdir, origPkg: {}, project: null as any },
|
|
104
|
+
{ type: 'mark-unsupported', features: ['decoration'] },
|
|
105
|
+
)
|
|
106
|
+
const content = fs.readFileSync(path.join(tmpdir, 'src', 'ext.js'), 'utf-8')
|
|
107
|
+
expect(content).not.toContain('TODO')
|
|
108
|
+
})
|
|
109
|
+
})
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import * as os from 'os'
|
|
4
|
+
import * as fs from 'fs'
|
|
5
|
+
|
|
6
|
+
describe('snippets step', () => {
|
|
7
|
+
let tmpdir: string
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'snippets-test-'))
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
fs.rmSync(tmpdir, { recursive: true, force: true })
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
function writeFile(relPath: string, content: string) {
|
|
18
|
+
const fp = path.join(tmpdir, relPath)
|
|
19
|
+
fs.mkdirSync(path.dirname(fp), { recursive: true })
|
|
20
|
+
fs.writeFileSync(fp, content)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
it('detects missing contributes.snippets in source and throws', async () => {
|
|
24
|
+
const { snippetsGenerator } = await import('./snippets.js')
|
|
25
|
+
expect(() => {
|
|
26
|
+
snippetsGenerator.generate(
|
|
27
|
+
{ input: tmpdir, output: tmpdir + '/out', origPkg: {}, project: null as any },
|
|
28
|
+
{ type: 'snippets' },
|
|
29
|
+
)
|
|
30
|
+
}).toThrow('has no contributes.snippets')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('copies snippet files and generates index.ts', async () => {
|
|
34
|
+
writeFile('snippets/javascript.json', JSON.stringify({ "console.log": { "prefix": "log", "body": "console.log($1)" } }))
|
|
35
|
+
writeFile('snippets/typescript.json', JSON.stringify({ "console.log": { "prefix": "log", "body": "console.log($1)" } }))
|
|
36
|
+
const outdir = tmpdir + '/out'
|
|
37
|
+
const { snippetsGenerator } = await import('./snippets.js')
|
|
38
|
+
const result = snippetsGenerator.generate(
|
|
39
|
+
{
|
|
40
|
+
input: tmpdir,
|
|
41
|
+
output: outdir,
|
|
42
|
+
origPkg: {
|
|
43
|
+
contributes: {
|
|
44
|
+
snippets: [
|
|
45
|
+
{ language: 'javascript', path: './snippets/javascript.json' },
|
|
46
|
+
{ language: 'typescript', path: './snippets/typescript.json' },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
project: null as any,
|
|
51
|
+
},
|
|
52
|
+
{ type: 'snippets' },
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
expect(result.generatedFiles).toHaveLength(1)
|
|
56
|
+
expect(result.generatedFiles[0].path).toBe('src/index.ts')
|
|
57
|
+
expect(result.generatedFiles[0].content).toContain('activate')
|
|
58
|
+
expect(result.generatedFiles[0].content).toContain('coc.nvim')
|
|
59
|
+
|
|
60
|
+
expect(result.activationEvents).toContain('onLanguage:javascript')
|
|
61
|
+
expect(result.activationEvents).toContain('onLanguage:typescript')
|
|
62
|
+
|
|
63
|
+
expect(fs.existsSync(path.join(outdir, 'snippets/javascript.json'))).toBe(true)
|
|
64
|
+
expect(fs.existsSync(path.join(outdir, 'snippets/typescript.json'))).toBe(true)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('uses languages override when specified', async () => {
|
|
68
|
+
writeFile('snippets/javascript.json', JSON.stringify({ test: { prefix: 't', body: 'test' } }))
|
|
69
|
+
writeFile('snippets/vue.json', JSON.stringify({ test: { prefix: 't', body: 'test' } }))
|
|
70
|
+
const outdir = tmpdir + '/out'
|
|
71
|
+
const { snippetsGenerator } = await import('./snippets.js')
|
|
72
|
+
const result = snippetsGenerator.generate(
|
|
73
|
+
{
|
|
74
|
+
input: tmpdir,
|
|
75
|
+
output: outdir,
|
|
76
|
+
origPkg: {
|
|
77
|
+
contributes: {
|
|
78
|
+
snippets: [
|
|
79
|
+
{ language: 'javascript', path: './snippets/javascript.json' },
|
|
80
|
+
{ language: 'vue', path: './snippets/vue.json' },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
project: null as any,
|
|
85
|
+
},
|
|
86
|
+
{ type: 'snippets', languages: ['javascript'] },
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
expect(result.activationEvents).toEqual(['onLanguage:javascript'])
|
|
90
|
+
expect(fs.existsSync(path.join(outdir, 'snippets/vue.json'))).toBe(false)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('throws when no snippet files are found', async () => {
|
|
94
|
+
const outdir = tmpdir + '/out'
|
|
95
|
+
const { snippetsGenerator } = await import('./snippets.js')
|
|
96
|
+
expect(() => {
|
|
97
|
+
snippetsGenerator.generate(
|
|
98
|
+
{
|
|
99
|
+
input: tmpdir,
|
|
100
|
+
output: outdir,
|
|
101
|
+
origPkg: {
|
|
102
|
+
contributes: {
|
|
103
|
+
snippets: [
|
|
104
|
+
{ language: 'javascript', path: './snippets/javascript.json' },
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
project: null as any,
|
|
109
|
+
},
|
|
110
|
+
{ type: 'snippets' },
|
|
111
|
+
)
|
|
112
|
+
}).toThrow('no snippet files were copied')
|
|
113
|
+
})
|
|
114
|
+
})
|