diff --git a/server/src/__tests__/embedded-languages.test.ts b/server/src/__tests__/embedded-languages.test.ts index c1bd35c0..46a85296 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 94c17839..a5e44df3 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 => {