Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Add more completion items & Tests #26

Merged
merged 16 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions client/src/language/languageClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import * as path from 'path'
import {
workspace,
type ExtensionContext,
window
window,
ConfigurationTarget
} from 'vscode'

import {
Expand Down Expand Up @@ -55,7 +56,7 @@ export async function activateLanguageServer (context: ExtensionContext): Promis
if (param.filePath?.endsWith('.conf') === true) {
const doc = await workspace.openTextDocument(param.filePath)
const { languageId } = doc
// The modifications from other extensions may happen later than this handler, hence the setTimtOut
// The modifications from other extensions may happen later than this handler, hence the setTimeOut
setTimeout(() => {
if (languageId !== 'bitbake') {
void window.showErrorMessage(`Failed to associate this file (${param.filePath}) with BitBake Language mode. Current language mode: ${languageId}. Please make sure there is no other extension that is causing the conflict. (e.g. Txt Syntax)`)
Expand All @@ -64,6 +65,16 @@ export async function activateLanguageServer (context: ExtensionContext): Promis
}
})

// Enable suggestions when inside strings, but server side disables suggestions on pure string content, they are onlyavailable in the variable expansion
window.onDidChangeActiveTextEditor((editor) => {
if (editor !== null && editor?.document.languageId === 'bitbake') {
void workspace.getConfiguration('editor').update('quickSuggestions', { strings: true }, ConfigurationTarget.Workspace)
} else {
// Reset to default settings
void workspace.getConfiguration('editor').update('quickSuggestions', { strings: false }, ConfigurationTarget.Workspace)
}
})

// Start the client and launch the server
await client.start()

Expand Down
106 changes: 106 additions & 0 deletions server/src/__tests__/analyzer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { generateParser } from '../tree-sitter/parser'
WilsonZiweiWang marked this conversation as resolved.
Show resolved Hide resolved
import Analyzer from '../tree-sitter/analyzer'
import { FIXTURE_DOCUMENT } from './fixtures/fixtures'

// Needed as the param
const DUMMY_URI = 'dummy_uri'

async function getAnalyzer (): Promise<Analyzer> {
const parser = await generateParser()
const analyzer = new Analyzer()
analyzer.initialize(parser)
return analyzer
}

const initialize = jest.spyOn(Analyzer.prototype, 'initialize')
const wordAtPoint = jest.spyOn(Analyzer.prototype, 'wordAtPoint')

describe('analyze', () => {
it('instantiates an analyzer', async () => {
// Alternative: Spy on something (logger) within the analyzer instead of spying on every function in the Analyzer
await getAnalyzer()

expect(initialize).toHaveBeenCalled()
})

it('analyzes simple correct bb file', async () => {
const analyzer = await getAnalyzer()
const diagnostics = await analyzer.analyze({
uri: DUMMY_URI,
document: FIXTURE_DOCUMENT.CORRECT
})
expect(diagnostics).toEqual([])
})

it('analyzes the document and returns global declarations', async () => {
const analyzer = await getAnalyzer()
await analyzer.analyze({
uri: DUMMY_URI,
document: FIXTURE_DOCUMENT.DECLARATION
})

const globalDeclarations = analyzer.getGlobalDeclarationSymbols(DUMMY_URI)

expect(globalDeclarations).toMatchInlineSnapshot(`
deribaucourt marked this conversation as resolved.
Show resolved Hide resolved
[
{
"kind": 13,
"location": {
"range": {
"end": {
"character": 11,
"line": 0,
},
"start": {
"character": 0,
"line": 0,
},
},
"uri": "${DUMMY_URI}",
},
"name": "FOO",
},
{
"kind": 13,
"location": {
"range": {
"end": {
"character": 11,
"line": 1,
},
"start": {
"character": 0,
"line": 1,
},
},
"uri": "${DUMMY_URI}",
},
"name": "BAR",
},
]
`)
})

it('analyzes the document and returns word at point', async () => {
const analyzer = await getAnalyzer()
await analyzer.analyze({
uri: DUMMY_URI,
document: FIXTURE_DOCUMENT.DECLARATION
})

const word1 = analyzer.wordAtPoint(
DUMMY_URI,
0,
0
)
const word2 = analyzer.wordAtPoint(
DUMMY_URI,
1,
0
)

expect(wordAtPoint).toHaveBeenCalled()
expect(word1).toEqual('FOO')
expect(word2).toEqual('BAR')
})
})
116 changes: 116 additions & 0 deletions server/src/__tests__/completions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { onCompletionHandler } from '../connectionHandlers/onCompletion'
import { analyzer } from '../tree-sitter/analyzer'
import { FIXTURE_DOCUMENT } from './fixtures/fixtures'
import { generateParser } from '../tree-sitter/parser'

const DUMMY_URI = 'dummy_uri'

/**
* The onCompletion handler doesn't allow other parameters, so we can't pass the analyzer and therefore the same
* instance used in the handler is used here. Documents are reset before each test for a clean state.
* A possible alternative is making the entire server a class and the analyzer a member
*/
describe('On Completion', () => {
beforeAll(async () => {
if (!analyzer.hasParser()) {
const parser = await generateParser()
analyzer.initialize(parser)
}
analyzer.resetAnalyzedDocuments()
})

beforeEach(() => {
analyzer.resetAnalyzedDocuments()
})

it('expects reserved variables, keywords and snippets in completion item lists', async () => {
// nothing is analyzed yet, only the static completion items are provided
const result = onCompletionHandler({
textDocument: {
uri: DUMMY_URI
},
position: {
line: 0,
character: 1
}
})

expect('length' in result).toBe(true)

expect(result).toEqual(
expect.arrayContaining([
{
kind: 14,
label: 'python'
}
])
)

expect(result).toEqual(
expect.arrayContaining([
{
kind: 6,
label: 'DESCRIPTION'
}
])
)

expect(result).toEqual(
/* eslint-disable no-template-curly-in-string */
expect.arrayContaining([
{
documentation: {
value: '```man\ndo_bootimg (bitbake-language-server)\n\n\n```\n```bitbake\ndef do_bootimg():\n\t# Your code here\n\t${1:pass}\n```\n---\nCreates a bootable live image. See the IMAGE_FSTYPES variable for additionalinformation on live image types.\n\n[Reference](https://docs.yoctoproject.org/singleindex.html#do-bootimg)',
deribaucourt marked this conversation as resolved.
Show resolved Hide resolved
kind: 'markdown'
},
insertText: [
'def do_bootimg():',
'\t# Your code here',
'\t${1:pass}'
].join('\n'),
insertTextFormat: 2,
label: 'do_bootimg',
kind: 15
}
])
)
})

it("doesn't provide suggestions when it is pure string content", async () => {
await analyzer.analyze({
uri: DUMMY_URI,
document: FIXTURE_DOCUMENT.COMPLETION
})

const result = onCompletionHandler({
textDocument: {
uri: DUMMY_URI
},
position: {
line: 1,
character: 10
}
})

expect(result).toEqual([])
})

it('provides suggestions when it is in variable expansion', async () => {
await analyzer.analyze({
uri: DUMMY_URI,
document: FIXTURE_DOCUMENT.COMPLETION
})

const result = onCompletionHandler({
textDocument: {
uri: DUMMY_URI
},
position: {
line: 1,
character: 13
}
})

expect(result).not.toEqual([])
})
})
2 changes: 2 additions & 0 deletions server/src/__tests__/fixtures/completion.bb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FOO = '123'
MYVAR = 'F${F}'
5 changes: 5 additions & 0 deletions server/src/__tests__/fixtures/correct.bb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
SUMMARY = "i.MX M4 core Demo images"

python do_foo(){
print '123'
}
2 changes: 2 additions & 0 deletions server/src/__tests__/fixtures/declarations.bb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FOO = '123'
BAR = '456'
35 changes: 35 additions & 0 deletions server/src/__tests__/fixtures/fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Inspired by bash-language-sever
* Repo: https://github.com/bash-lsp/bash-language-server
*/

import path from 'path'
import fs from 'fs'
import { TextDocument } from 'vscode-languageserver-textdocument'

const FIXTURE_FOLDER = path.join(__dirname, './')

type FIXTURE_URI_KEY = keyof typeof FIXTURE_URI

function getDocument (uri: string): TextDocument {
return TextDocument.create(
uri,
'bitbake',
0,
fs.readFileSync(uri.replace('file://', ''), 'utf8')
)
}

export const FIXTURE_URI = {
CORRECT: `file://${path.join(FIXTURE_FOLDER, 'correct.bb')}`,
DECLARATION: `file://${path.join(FIXTURE_FOLDER, 'declarations.bb')}`,
COMPLETION: `file://${path.join(FIXTURE_FOLDER, 'completion.bb')}`

}

export const FIXTURE_DOCUMENT: Record<FIXTURE_URI_KEY, TextDocument> = (
Object.keys(FIXTURE_URI) as FIXTURE_URI_KEY[]
).reduce<any>((acc, cur: FIXTURE_URI_KEY) => {
acc[cur] = getDocument(FIXTURE_URI[cur])
return acc
}, {})
Loading
Loading