diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 84e9f0ba1..07d4c4a19 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -54,4 +54,4 @@ jobs: uses: actions/upload-artifact@v3 with: name: vscode-bitbake - path: client/bitbake*.vsix + path: client/yocto-bitbake*.vsix diff --git a/.vscode/launch.json b/.vscode/launch.json index 861fc9cd6..13e54674e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -49,7 +49,6 @@ "args": [ "--extensionDevelopmentPath=${workspaceRoot}/client", "--extensionTestsPath=${workspaceRoot}/integration-tests/out/index", - "--disable-extensions", "--disable-workspace-trust", "${workspaceRoot}/integration-tests/project-folder" ], diff --git a/README.md b/README.md index fb5254ce9..9e7507f2c 100644 --- a/README.md +++ b/README.md @@ -19,15 +19,15 @@ Manual installation takes place in two steps. The code must be installed via `np ### Commands To install the dependencies: -``` +``` sh npm install ``` To compile the typescript files: -``` +``` sh npm run compile ``` To clean up the project (This deletes node_modules): -``` +``` sh npm run clean ``` For more commands, refer to the `script` section in the root `package.json`. @@ -37,32 +37,35 @@ Press `F5` or navigate to the debug section on the left of the VS Code and selec ## Testing -Bitbake and Yocto docs are required for some features to work, They need to be fetched before testing and development: - - $ npm run fetch:docs - +BitBake and Yocto docs are required for some features to work. They need to be fetched before testing and development: +``` sh +npm run fetch:docs +``` Similar for the command that fetches poky, it needs to be run before running the integration tests: - -$ npm run fetch:poky - +``` sh +npm run fetch:poky +``` A wrapper npm script allows running several kinds of tests. To run all tests, use: - - $ npm test - +``` sh +npm test +``` All the tests mentionned are run in our GitHub CI. ### Linter tests -One can check coding style using `npm run lint`. +One can check coding style using +``` sh +npm run lint +``` Install the recommended extensions to automatically fix linting errors when possible. ### Unit tests Unit tests are powered by Jest. They allow mocking the behavior of VSCode and other external libraries. They can individually be run with: - - $ npm run jest - +```sh +npm run jest +``` Unit tests are defined in the `__tests__` folders. If you have installed the recommended extensions, you'll find launch and debug @@ -74,7 +77,7 @@ See [the individual grammar tests README](client/test/grammars/README.md). ### Integration tests -These tests allow running the bitbake extension in a live VSCode environment. +These tests allow running the BitBake extension in a live VSCode environment. See [the individual integration tests README](integration-tests/README.md). ## Tree-sitter diff --git a/client/README.md b/client/README.md index 08674237f..1e87391cc 100644 --- a/client/README.md +++ b/client/README.md @@ -19,7 +19,7 @@ Here's an example `settings.json` reflecting the default values: ### Syntax highlighting -The extension provides syntax highlighting for BitBake recipes, classes, configuration and inc-files. Syntax highlighting also supports embedded languages inside bitbake recipes including inline Python variable expansion, shell code and Python code. +The extension provides syntax highlighting for BitBake recipes, classes, configuration and inc-files. Syntax highlighting also supports embedded languages inside BitBake recipes including inline Python variable expansion, shell code and Python code. The BitBake language is automatically detected based on the file extension: [`.bb`, `.bbappend`, `.bbclass`]. [`.conf`, `.inc`] are also supported but may be used by other tools. @@ -105,7 +105,7 @@ By default, this extension will run BitBake in parse only mode in the background ### BitBake status bar -Bitbake parsing status is displayed in the status bar at the bottom of the screen. It will show wether the last BitBake run was successful or not. The bitbake server queues all BitBake commands and runs them sequentially. The status bar will show you if the extension is currently trying to access the bitbake server. +Bitbake parsing status is displayed in the status bar at the bottom of the screen. It will show wether the last BitBake run was successful or not. The BitBake server queues all BitBake commands and runs them sequentially. The status bar will show you if the extension is currently trying to access the bitbake server. ![Status bar](doc/status-bar.gif) diff --git a/client/package.json b/client/package.json index 5069e57b0..6fc4dc2e1 100644 --- a/client/package.json +++ b/client/package.json @@ -25,6 +25,10 @@ "engines": { "vscode": "^1.75.0" }, + "extensionDependencies": [ + "mads-hartmann.bash-ide-vscode", + "ms-python.python" + ], "categories": [ "Programming Languages" ], diff --git a/client/src/language/middlewareCompletion.ts b/client/src/language/middlewareCompletion.ts index fa4d768a1..9f79aaa00 100644 --- a/client/src/language/middlewareCompletion.ts +++ b/client/src/language/middlewareCompletion.ts @@ -3,18 +3,28 @@ * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ -import { type CompletionList, Uri, commands } from 'vscode' +import { type CompletionList, Uri, commands, Range } from 'vscode' import { type CompletionMiddleware } from 'vscode-languageclient/node' import { requestsManager } from './RequestManager' -import { getEmbeddedLanguageDocPosition } from './utils' +import { getEmbeddedLanguageDocPosition, getOriginalDocRange } from './utils' +import { getFileContent } from '../lib/src/utils/files' export const middlewareProvideCompletion: CompletionMiddleware['provideCompletionItem'] = async (document, position, context, token, next) => { const embeddedLanguageDocInfos = await requestsManager.getEmbeddedLanguageDocInfos(document.uri.toString(), position) if (embeddedLanguageDocInfos === undefined || embeddedLanguageDocInfos === null) { return await next(document, position, context, token) } - const adjustedPosition = await getEmbeddedLanguageDocPosition(document, embeddedLanguageDocInfos, position) + const embeddedLanguageDocContent = await getFileContent(Uri.parse(embeddedLanguageDocInfos.uri).fsPath) + if (embeddedLanguageDocContent === undefined) { + return + } + const adjustedPosition = getEmbeddedLanguageDocPosition( + document, + embeddedLanguageDocContent, + embeddedLanguageDocInfos.characterIndexes, + position + ) const vdocUri = Uri.parse(embeddedLanguageDocInfos.uri) const result = await commands.executeCommand( 'vscode.executeCompletionItemProvider', @@ -22,5 +32,19 @@ export const middlewareProvideCompletion: CompletionMiddleware['provideCompletio adjustedPosition, context.triggerCharacter ) + result.items.forEach((item) => { + if (item.range === undefined) { + // pass + } else if (item.range instanceof Range) { + item.range = getOriginalDocRange(document, embeddedLanguageDocContent, embeddedLanguageDocInfos.characterIndexes, item.range) + } else { + const inserting = getOriginalDocRange(document, embeddedLanguageDocContent, embeddedLanguageDocInfos.characterIndexes, item.range.inserting) + const replacing = getOriginalDocRange(document, embeddedLanguageDocContent, embeddedLanguageDocInfos.characterIndexes, item.range.replacing) + if (inserting === undefined || replacing === undefined) { + return + } + item.range = { inserting, replacing } + } + }) return result } diff --git a/client/src/language/middlewareHover.ts b/client/src/language/middlewareHover.ts index 51d21fe56..e237383fe 100644 --- a/client/src/language/middlewareHover.ts +++ b/client/src/language/middlewareHover.ts @@ -8,13 +8,23 @@ import { type Hover, Uri, commands } from 'vscode' import { requestsManager } from './RequestManager' import { getEmbeddedLanguageDocPosition } from './utils' +import { getFileContent } from '../lib/src/utils/files' export const middlewareProvideHover: HoverMiddleware['provideHover'] = async (document, position, token, next) => { const embeddedLanguageDocInfos = await requestsManager.getEmbeddedLanguageDocInfos(document.uri.toString(), position) if (embeddedLanguageDocInfos === undefined || embeddedLanguageDocInfos === null) { return await next(document, position, token) } - const adjustedPosition = await getEmbeddedLanguageDocPosition(document, embeddedLanguageDocInfos, position) + const embeddedLanguageDocContent = await getFileContent(Uri.parse(embeddedLanguageDocInfos.uri).fsPath) + if (embeddedLanguageDocContent === undefined) { + return + } + const adjustedPosition = getEmbeddedLanguageDocPosition( + document, + embeddedLanguageDocContent, + embeddedLanguageDocInfos.characterIndexes, + position + ) const vdocUri = Uri.parse(embeddedLanguageDocInfos.uri) const result = await commands.executeCommand( 'vscode.executeHoverProvider', diff --git a/client/src/language/utils.ts b/client/src/language/utils.ts index 5fbc27f96..71bb265a3 100644 --- a/client/src/language/utils.ts +++ b/client/src/language/utils.ts @@ -3,31 +3,45 @@ * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ -import fs from 'fs' +import { Position, Range, type TextDocument } from 'vscode' -import { type TextDocument, Position } from 'vscode' +export const getOriginalDocRange = ( + originalTextDocument: TextDocument, + embeddedLanguageDocContent: string, + characterIndexes: number[], + embeddedRange: Range +): Range | undefined => { + const start = getOriginalDocPosition(originalTextDocument, embeddedLanguageDocContent, characterIndexes, embeddedRange.start) + const end = getOriginalDocPosition(originalTextDocument, embeddedLanguageDocContent, characterIndexes, embeddedRange.end) + if (start === undefined || end === undefined) { + return + } + return new Range(start, end) +} -import { type EmbeddedLanguageDocInfos } from '../lib/src/types/embedded-languages' -import { logger } from '../lib/src/utils/OutputLogger' +const getOriginalDocPosition = ( + originalTextDocument: TextDocument, + embeddedLanguageDocContent: string, + characterIndexes: number[], + embeddedPosition: Position +): Position | undefined => { + const embeddedLanguageOffset = getOffset(embeddedLanguageDocContent, embeddedPosition) + const originalOffset = characterIndexes.findIndex(index => index === embeddedLanguageOffset) + if (originalOffset === -1) { + return + } + return originalTextDocument.positionAt(originalOffset) +} -export const getEmbeddedLanguageDocPosition = async ( +export const getEmbeddedLanguageDocPosition = ( originalTextDocument: TextDocument, - embeddedLanguageDocInfos: EmbeddedLanguageDocInfos, + embeddedLanguageDocContent: string, + characterIndexes: number[], originalPosition: Position -): Promise => { +): Position => { const originalOffset = originalTextDocument.offsetAt(originalPosition) - const embeddedLanguageDocOffset = embeddedLanguageDocInfos.characterIndexes[originalOffset] - try { - const embeddedLanguageDocContent = await new Promise((resolve, reject) => { - fs.readFile(embeddedLanguageDocInfos.uri.replace('file://', ''), { encoding: 'utf-8' }, - (error, data) => { error !== null ? reject(error) : resolve(data) } - ) - }) - return getPosition(embeddedLanguageDocContent, embeddedLanguageDocOffset) - } catch (error) { - logger.error(`Failed to get embedded language document position: ${error as any}`) - return undefined - } + const embeddedLanguageDocOffset = characterIndexes[originalOffset] + return getPosition(embeddedLanguageDocContent, embeddedLanguageDocOffset) } const getPosition = (documentContent: string, offset: number): Position => { @@ -43,3 +57,12 @@ const getPosition = (documentContent: string, offset: number): Position => { } return new Position(line, character) } + +const getOffset = (documentContent: string, position: Position): number => { + let offset = 0 + for (let i = 0; i < position.line; i++) { + offset = documentContent.indexOf('\n', offset) + 1 + } + offset += position.character + return offset +} diff --git a/client/src/lib/src/utils/files.ts b/client/src/lib/src/utils/files.ts new file mode 100644 index 000000000..8b3f10ad1 --- /dev/null +++ b/client/src/lib/src/utils/files.ts @@ -0,0 +1,20 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2023 Savoir-faire Linux. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import fs from 'fs' + +import { logger } from './OutputLogger' + +export const getFileContent = async (path: string): Promise => { + const fileContent = await new Promise((resolve, reject) => { + fs.readFile(path, { encoding: 'utf-8' }, + (error, data) => { error !== null ? reject(error) : resolve(data) } + ) + }).catch(err => { + logger.error(`Could not open file: ${err}`) + return undefined + }) + return fileContent +} diff --git a/client/syntaxes/bitbake.tmLanguage.json b/client/syntaxes/bitbake.tmLanguage.json index c75cee0f4..ba2e96545 100644 --- a/client/syntaxes/bitbake.tmLanguage.json +++ b/client/syntaxes/bitbake.tmLanguage.json @@ -100,6 +100,25 @@ }, "string": { "patterns": [ + { + "name": "string.quoted.triple.bb", + "begin": "(\"\"\")", + "end": "(\"\"\")", + "patterns": [ + { + "include": "#escaped-single-quote" + }, + { + "include": "#escaped-double-quote" + }, + { + "include": "#inline-python" + }, + { + "include": "#variable-expansion" + } + ] + }, { "name": "string.quoted.double.bb", "begin": "(\")", diff --git a/client/test/grammars/snaps/strings.bb b/client/test/grammars/snaps/strings.bb index ae44045ba..1a122e7b7 100644 --- a/client/test/grammars/snaps/strings.bb +++ b/client/test/grammars/snaps/strings.bb @@ -51,3 +51,9 @@ INHERIT += "autotools pkgconfig" MYVAR = "This string contains escaped double quote \" and it should not break the highlight" MYVAR = 'This string contains escaped single quote \' and it should not break the highlight' + +MYVAR = """ +nested " quotes shoudn't change the highlighting +""" + +TEST_TRIPLE_QUOTES = 'the highlighting for this line which follows the triple quotes should still work correctly' diff --git a/client/test/grammars/test-cases/strings.bb b/client/test/grammars/test-cases/strings.bb index 5f10265f2..c606b18d4 100644 --- a/client/test/grammars/test-cases/strings.bb +++ b/client/test/grammars/test-cases/strings.bb @@ -143,3 +143,18 @@ # ^^ source.bb string.quoted.single.bb constant.character.escape.bb # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ source.bb string.quoted.single.bb # ^ source.bb string.quoted.single.bb + +>MYVAR = """ +# ^^^ source.bb string.quoted.triple.bb +>nested " quotes shoudn't change the highlighting +#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ source.bb string.quoted.triple.bb +>""" +#^^^ source.bb string.quoted.triple.bb +> +>TEST_TRIPLE_QUOTES = 'the highlighting for this line which follows the triple quotes should still work correctly' +#^^^^^^^^^^^^^^^^^^ source.bb variable.other.names.bb +# ^ source.bb keyword.operator.bb +# ^ source.bb string.quoted.single.bb +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ source.bb string.quoted.single.bb +# ^ source.bb string.quoted.single.bb +> \ No newline at end of file diff --git a/integration-tests/README.md b/integration-tests/README.md index 9d6a9fe27..339bcceff 100644 --- a/integration-tests/README.md +++ b/integration-tests/README.md @@ -1,6 +1,6 @@ # Integration tests -The integration tests allow running the bitbake extension in a live VSCode +The integration tests allow running the extension in a live VSCode environment. ## Running the integration tests @@ -13,7 +13,9 @@ This can be done by running our `npm fetch:poky` script. It requires `curl` and The npm script `test:integration` will run the integration tests. It will run VSCode in headless mode. Make sure to have installed the dependency: - $ apt install xvfb +``` sh +apt install xvfb +``` ## Debugging the integration tests diff --git a/integration-tests/remote-session/README.md b/integration-tests/remote-session/README.md index bb39587cc..8e265791a 100644 --- a/integration-tests/remote-session/README.md +++ b/integration-tests/remote-session/README.md @@ -20,4 +20,4 @@ This test is unfortunately not automated yet. 4. Connect to the container using the Remote SSH extension. An example ssh.config file is provided. 5. Install the extension on the remote container: Extensions->Local->Bitbake->Install in SSH 6. Within the remote session, open the workspace in `/home/yoctouser/vscode-bitbake/integration-tests/project-folder` -7. Open recipes, run bitbake commands... +7. Open recipes, run BitBake commands... diff --git a/integration-tests/src/runTest.ts b/integration-tests/src/runTest.ts index d9a609cb8..a2ec5dde1 100644 --- a/integration-tests/src/runTest.ts +++ b/integration-tests/src/runTest.ts @@ -3,12 +3,31 @@ * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ +import * as cp from 'child_process' import * as path from 'path' - -import { runTests } from '@vscode/test-electron' +import { + downloadAndUnzipVSCode, + resolveCliArgsFromVSCodeExecutablePath, + runTests +} from '@vscode/test-electron' async function main (): Promise { try { + const vscodeExecutablePath = await downloadAndUnzipVSCode('1.84.2') + const [cliPath, ...args] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath) + + cp.spawnSync( + cliPath, + [ + ...args, + '--install-extension', 'mads-hartmann.bash-ide-vscode@1.39.0', + '--install-extension', 'ms-python.python@2023.20.0' + ], + { + encoding: 'utf-8', + stdio: 'inherit' + } + ) // The folder containing the Extension Manifest package.json // Passed to `--extensionDevelopmentPath` const extensionDevelopmentPath = path.resolve(__dirname, '../../client/') @@ -19,11 +38,17 @@ async function main (): Promise { const testWorkspace = path.resolve(__dirname, '../../integration-tests/project-folder') - const launchArgs = ['--disable-extensions', '--disable-workspace-trust', testWorkspace] + const launchArgs = ['--disable-workspace-trust', testWorkspace] const extensionTestsEnv = {} // Download VS Code, unzip it and run the integration test - await runTests({ launchArgs, extensionDevelopmentPath, extensionTestsPath, extensionTestsEnv }) + await runTests({ + launchArgs, + vscodeExecutablePath, + extensionDevelopmentPath, + extensionTestsPath, + extensionTestsEnv + }) } catch (err) { console.error(err) console.error('Failed to run tests') diff --git a/server/src/__tests__/embedded-languages.test.ts b/server/src/__tests__/embedded-languages.test.ts index c1bd35c0f..46a852964 100644 --- a/server/src/__tests__/embedded-languages.test.ts +++ b/server/src/__tests__/embedded-languages.test.ts @@ -13,6 +13,7 @@ import { generateParser } from '../tree-sitter/parser' import { FIXTURE_DOCUMENT } from './fixtures/fixtures' import { TextDocument } from 'vscode-languageserver-textdocument' import { type EmbeddedLanguageType } from '../lib/src/types/embedded-languages' +import { imports } from '../embedded-languages/python-support' describe('Embedded Language Documents file management', () => { beforeAll(async () => { @@ -117,22 +118,22 @@ describe('Create various basic embedded python documents', () => { [ 'anonymous', 'python(){\n pass\n}', - 'def _ ():\n pass\n ' + `${imports}def _ ():\n pass\n ` ], [ 'named with python keyword', 'python foo (){\n pass\n}', - 'def foo ():\n pass\n ' + `${imports}def foo ():\n pass\n ` ], [ 'empty', 'python(){\n}', - 'def _ ():\n pass\n ' + `${imports}def _ ():\n pass\n ` ], [ 'with def keyword', 'def foo():\n pass', - 'def foo():\n pass' + `${imports}def foo():\n pass` ] ])('%s', async (description, input, result) => { const embeddedContent = await createEmbeddedContent(input, 'python') @@ -155,31 +156,31 @@ describe('Create Python embedded language content with inline Python', () => { 'basic', // eslint-disable-next-line no-template-curly-in-string 'FOO = \'${@"BAR"}\'', - ' \n\n"BAR"\n ' + `${imports} \n\n"BAR"\n ` ], [ 'with spacing', // eslint-disable-next-line no-template-curly-in-string 'FOO = \'${@ "BAR" }\'', - ' \n \n"BAR" \n ' + `${imports} \n \n"BAR" \n ` ], [ 'multiline', // eslint-disable-next-line no-template-curly-in-string 'FOO = \'${@"BAR"}\' \\\n1 \\\n2"', - ' \n\n"BAR"\n \n \n ' + `${imports} \n\n"BAR"\n \n \n ` ], [ 'with two embedded python regions', // eslint-disable-next-line no-template-curly-in-string 'FOO = \'${@"BAR"}${@"BAR"}\'', - ' \n\n"BAR"\n \n\n"BAR"\n ' + `${imports} \n\n"BAR"\n \n\n"BAR"\n ` ], [ 'without surrounding quotes', // eslint-disable-next-line no-template-curly-in-string 'inherit ${@"test"}', - ' \n\n"test"\n' + `${imports} \n\n"test"\n` ] /* // This is not yet supported by tree-sitter [ @@ -194,48 +195,6 @@ describe('Create Python embedded language content with inline Python', () => { }) }) -describe('Create Python embedded language content with imports', () => { - beforeAll(async () => { - if (!analyzer.hasParser()) { - const parser = await generateParser() - analyzer.initialize(parser) - } - analyzer.resetAnalyzedDocuments() - await embeddedLanguageDocsManager.setStoragePath(__dirname) - }) - - test.each([ - [ - 'with bb', - 'python(){\n bb.parse.vars_from_file("test")\n}', - 'import bb\nfrom bb import parse\nbb.parse = parse\ndef _ ():\n bb.parse.vars_from_file("test")\n ' - ], - [ - 'with d', - 'python(){\n d.getVar("test")\n}', - 'from bb import data_smart\nd = data_smart.DataSmart()\ndef _ ():\n d.getVar("test")\n ' - ], - [ - 'with e', - 'python(){\n e.data.getVar("test")\n}', - 'from bb import data_smart\nd = data_smart.DataSmart()\nfrom bb import event\ne = event.Event()\ne.data = d\ndef _ ():\n e.data.getVar("test")\n ' - ], - [ - 'with os', - 'python(){\n os.path.dirname("test")\n}', - 'import os\ndef _ ():\n os.path.dirname("test")\n ' - ], - [ - 'with combination (d and bb)', - 'python(){\n d.getVar("test")\n bb.parse.vars_from_file("test")\n}', - 'from bb import data_smart\nd = data_smart.DataSmart()\nimport bb\nfrom bb import parse\nbb.parse = parse\ndef _ ():\n d.getVar("test")\n bb.parse.vars_from_file("test")\n ' - ] - ])('%s', async (description, input, result) => { - const embeddedContent = await createEmbeddedContent(input, 'python') - expect(embeddedContent).toEqual(result) - }) -}) - const createEmbeddedContent = async (content: string, language: EmbeddedLanguageType): Promise => { const uri = randomUUID() const document = TextDocument.create(uri, 'bitbake', 1, content) @@ -253,7 +212,7 @@ const createEmbeddedContent = async (content: string, language: EmbeddedLanguage } const expectedPythonEmbeddedLanguageDoc = -` +`${imports} def do_foo(): print('123') diff --git a/server/src/embedded-languages/python-support.ts b/server/src/embedded-languages/python-support.ts index 94c178395..a5e44df36 100644 --- a/server/src/embedded-languages/python-support.ts +++ b/server/src/embedded-languages/python-support.ts @@ -12,44 +12,52 @@ import * as TreeSitterUtils from '../tree-sitter/utils' import { embeddedLanguageDocsManager } from './documents-manager' import { type EmbeddedLanguageDoc, insertTextIntoEmbeddedLanguageDoc, initEmbeddedLanguageDoc } from './utils' +export const imports = [ + 'import bb', + 'from bb import data_smart', + 'd = data_smart.DataSmart()', + 'from bb import event', + 'e = event.Event()', + 'e.data = d', + 'import os', + '' +].join('\n') + export const generatePythonEmbeddedLanguageDoc = async (textDocument: TextDocument): Promise => { const analyzedDocument = analyzer.getAnalyzedDocument(textDocument.uri) if (analyzedDocument === undefined) { return } - const imports = new Set() const embeddedLanguageDoc = initEmbeddedLanguageDoc(textDocument, 'python') TreeSitterUtils.forEach(analyzedDocument.tree.rootNode, (node) => { switch (node.type) { case 'python_function_definition': - handlePythonFunctionDefinition(node, embeddedLanguageDoc, imports) + handlePythonFunctionDefinition(node, embeddedLanguageDoc) return false case 'anonymous_python_function': - handleAnonymousPythonFunction(node, embeddedLanguageDoc, imports) + handleAnonymousPythonFunction(node, embeddedLanguageDoc) return false case 'inline_python': - handleInlinePythonNode(node, embeddedLanguageDoc, imports) + handleInlinePythonNode(node, embeddedLanguageDoc) return false default: return true } }) - if (imports.size !== 0) { - insertTextIntoEmbeddedLanguageDoc(embeddedLanguageDoc, 0, 0, [...imports].join('\n') + '\n') - } + insertTextIntoEmbeddedLanguageDoc(embeddedLanguageDoc, 0, 0, imports) await embeddedLanguageDocsManager.saveEmbeddedLanguageDoc(embeddedLanguageDoc) } -const handlePythonFunctionDefinition = (node: SyntaxNode, embeddedLanguageDoc: EmbeddedLanguageDoc, imports: Set): void => { +const handlePythonFunctionDefinition = (node: SyntaxNode, embeddedLanguageDoc: EmbeddedLanguageDoc): void => { insertTextIntoEmbeddedLanguageDoc(embeddedLanguageDoc, node.startIndex, node.endIndex, node.text) node.children.forEach((child) => { if (child.type === 'block') { - handleBlockNode(child, embeddedLanguageDoc, imports) + handleBlockNode(child, embeddedLanguageDoc) } }) } -const handleAnonymousPythonFunction = (node: SyntaxNode, embeddedLanguageDoc: EmbeddedLanguageDoc, imports: Set): void => { +const handleAnonymousPythonFunction = (node: SyntaxNode, embeddedLanguageDoc: EmbeddedLanguageDoc): void => { insertTextIntoEmbeddedLanguageDoc(embeddedLanguageDoc, node.startIndex, node.endIndex, node.text) node.children.forEach((child) => { switch (child.type) { @@ -72,7 +80,7 @@ const handleAnonymousPythonFunction = (node: SyntaxNode, embeddedLanguageDoc: Em insertTextIntoEmbeddedLanguageDoc(embeddedLanguageDoc, child.startIndex, child.endIndex, ' ') break case 'block': - handleBlockNode(child, embeddedLanguageDoc, imports) + handleBlockNode(child, embeddedLanguageDoc) break default: break @@ -80,7 +88,7 @@ const handleAnonymousPythonFunction = (node: SyntaxNode, embeddedLanguageDoc: Em }) } -const handleInlinePythonNode = (inlinePythonNode: SyntaxNode, embeddedLanguageDoc: EmbeddedLanguageDoc, imports: Set): void => { +const handleInlinePythonNode = (inlinePythonNode: SyntaxNode, embeddedLanguageDoc: EmbeddedLanguageDoc): void => { const openingNode = inlinePythonNode.child(0) const pythonContentNode = inlinePythonNode.child(1) const closingNode = inlinePythonNode.child(2) @@ -98,56 +106,13 @@ const handleInlinePythonNode = (inlinePythonNode: SyntaxNode, embeddedLanguageDo insertTextIntoEmbeddedLanguageDoc(embeddedLanguageDoc, pythonContentNode.startIndex, pythonContentNode.startIndex, '\n') // prevent trailing spaces insertTextIntoEmbeddedLanguageDoc(embeddedLanguageDoc, pythonContentNode.startIndex, pythonContentNode.endIndex, pythonContentNode.text) insertTextIntoEmbeddedLanguageDoc(embeddedLanguageDoc, closingNode.startIndex, closingNode.endIndex, '\n') - handleBlockNode(pythonContentNode, embeddedLanguageDoc, imports) + handleBlockNode(pythonContentNode, embeddedLanguageDoc) } -const handleBlockNode = (blockNode: SyntaxNode, embeddedLanguageDoc: EmbeddedLanguageDoc, imports: Set): void => { +const handleBlockNode = (blockNode: SyntaxNode, embeddedLanguageDoc: EmbeddedLanguageDoc): void => { if (blockNode.text === '') { insertTextIntoEmbeddedLanguageDoc(embeddedLanguageDoc, blockNode.startIndex, blockNode.endIndex, '\n pass') } - handleImports(blockNode, imports) -} - -const handleImports = (blockNode: SyntaxNode, imports: Set): void => { - const importBb = (bbNode: SyntaxNode): void => { - if (bbNode.nextSibling?.type === '.' && bbNode.nextNamedSibling?.type === 'python_identifier') { - const importName = bbNode.nextNamedSibling.text - imports.add('import bb') - imports.add(`from bb import ${importName}`) - imports.add(`bb.${importName} = ${importName}`) - } - } - - const importD = (): void => { - imports.add('from bb import data_smart') - imports.add('d = data_smart.DataSmart()') - } - - const importE = (): void => { - importD() - imports.add('from bb import event') - imports.add('e = event.Event()') - imports.add('e.data = d') - } - - const importOs = (): void => { - imports.add('import os') - } - - TreeSitterUtils.forEach(blockNode, (child) => { - if (child.type === 'python_identifier') { - if (child.text === 'bb') { - importBb(child) - } else if (child.text === 'd') { - importD() - } else if (child.text === 'e') { - importE() - } else if (child.text === 'os') { - importOs() - } - } - return true - }) } const handleOverrideNode = (overrideNode: SyntaxNode, embeddedLanguageDoc: EmbeddedLanguageDoc): void => {