diff --git a/server/src/DefinitionProvider.ts b/server/src/DefinitionProvider.ts deleted file mode 100644 index 1514028d..00000000 --- a/server/src/DefinitionProvider.ts +++ /dev/null @@ -1,192 +0,0 @@ -/* -------------------------------------------------------------------------------------------- - * Copyright (c) Eugen Wiens. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - * ------------------------------------------------------------------------------------------ */ - -import { - type Definition, - Location, - Range -} from 'vscode-languageserver' - -import { - type ElementInfo, - type PathInfo -} from './lib/src/types/BitbakeScanResult' - -import { - type SymbolScanner, - type SymbolContent -} from './SymbolScanner' - -import { logger } from './lib/src/utils/OutputLogger' - -import path from 'path' -import { bitBakeProjectScannerClient } from './BitbakeProjectScannerClient' - -export class DefinitionProvider { - private _symbolScanner: SymbolScanner | null = null - - // eslint-disable-next-line accessor-pairs - set symbolScanner (symbolScanner: SymbolScanner | null) { - this._symbolScanner = symbolScanner - } - - createDefinitionForKeyword (keyword: string, restOfLine: string, selectedSympbol?: string): Definition { - let definition: Definition = [] - restOfLine = restOfLine.trim() - - switch (keyword) { - case 'inherit': - { - let searchString: string - if (selectedSympbol === undefined) { - searchString = restOfLine - } else { - searchString = selectedSympbol - } - - const elementInfos: ElementInfo[] = bitBakeProjectScannerClient.bitbakeScanResult._classes.filter((obj: ElementInfo): boolean => { - return obj.name === searchString - }) - definition = this.createDefinitionForElementInfo(elementInfos) - } - break - - case 'require': - case 'include': - { - const includeFile: PathInfo = path.parse(restOfLine) - let elementInfos: ElementInfo[] = bitBakeProjectScannerClient.bitbakeScanResult._includes.filter((obj: ElementInfo): boolean => { - return obj.name === includeFile.name - }) - - if (elementInfos.length === 0) { - elementInfos = bitBakeProjectScannerClient.bitbakeScanResult._recipes.filter((obj: ElementInfo): boolean => { - return obj.name === includeFile.name - }) - } - definition = this.createDefinitionForElementInfo(elementInfos) - } - break - - default: - } - - return definition - } - - createDefinitionForSymbol (symbol: string): Definition { - let definitions: Definition = this.createDefinitionForSymbolRecipes(symbol) - - if (definitions === null) { - definitions = this.createDefinitionForSymbolVariables(symbol) - } - - return definitions - } - - private createDefinitionForSymbolRecipes (symbol: string): Definition { - let definitions: Definition = [] - - const recipe: ElementInfo | undefined = bitBakeProjectScannerClient.bitbakeScanResult._recipes.find((obj: ElementInfo): boolean => { - return obj.name === symbol - }) - - if (recipe?.path !== undefined) { - let definitionsList: PathInfo[] = new Array < PathInfo >(recipe.path) - - if ((recipe.appends !== undefined) && (recipe.appends.length > 0)) { - definitionsList = definitionsList.concat(recipe.appends) - } - definitions = this.createDefinitionLocationForPathInfoList(definitionsList) - } - - return definitions - } - - private createDefinitionForSymbolVariables (symbol: string): Definition { - let definitions: Definition = [] - - if (this._symbolScanner !== null) { - const symbols: SymbolContent[] = this._symbolScanner.symbols.filter((obj: SymbolContent): boolean => { - return obj.symbolName === symbol - }) - definitions = this.createDefinitionForSymbolContentList(symbols) - } else { - logger.debug(`Cannot create definitions for symbol ${symbol}: symbol scanner is null`) - } - - return definitions - } - - private createDefinitionForElementInfo (elementInfos: ElementInfo[]): Definition { - const definition: Definition = [] - - for (const elementInfo of elementInfos) { - logger.debug(`definition ${JSON.stringify(elementInfo)}`) - if (elementInfo.path !== undefined) { - const location: Location = this.createDefinitionLocationForPathInfo(elementInfo.path) - definition.push(location) - } - } - - return definition - } - - private createDefinitionLocationForPathInfoList (pathInfoList: PathInfo[]): Definition { - let definition: Definition = [] - - if ((pathInfoList !== undefined) && (pathInfoList.length > 0)) { - if (pathInfoList.length > 1) { - definition = new Array < Location >() - - for (const pathInfo of pathInfoList) { - logger.debug(`definition ${JSON.stringify(pathInfo)}`) - const location: Location = this.createDefinitionLocationForPathInfo(pathInfo) - - definition.push(location) - } - } else { - definition = this.createDefinitionLocationForPathInfo(pathInfoList[0]) - } - } - - return definition - } - - private createDefinitionLocationForPathInfo (path: PathInfo): Location { - const url: string = 'file://' + path.dir + '/' + path.base - const location: Location = Location.create(encodeURI(url), Range.create(0, 0, 0, 0)) - - return location - } - - private createDefinitionForSymbolContentList (symbolContent: SymbolContent[]): Definition { - const definition: Definition = [] - - for (const element of symbolContent) { - logger.debug(`definition ${JSON.stringify(element)}`) - const location = this.createDefinitionForSymbolContent(element) - if (location !== undefined) { - definition.push(location) - } - } - - return definition - } - - private createDefinitionForSymbolContent (symbolContent: SymbolContent): Location | undefined { - const url: string = 'file://' + symbolContent.filePath - if (symbolContent.lineNumber === undefined) { - return undefined - } - const range: Range = Range.create(symbolContent.lineNumber, symbolContent.startPosition, - symbolContent.lineNumber, symbolContent.endPostion - ) - - return Location.create(encodeURI(url), range) - } -} - -export const definitionProvider = new DefinitionProvider() diff --git a/server/src/SymbolScanner.ts b/server/src/SymbolScanner.ts deleted file mode 100644 index f01e7516..00000000 --- a/server/src/SymbolScanner.ts +++ /dev/null @@ -1,181 +0,0 @@ -/* -------------------------------------------------------------------------------------------- - * Copyright (c) Eugen Wiens. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - * ------------------------------------------------------------------------------------------ */ - -import fs from 'fs' - -import type { - Definition, - Location -} from 'vscode-languageserver' - -import type { - DefinitionProvider -} from './DefinitionProvider' - -import { logger } from './lib/src/utils/OutputLogger' -import { DIRECTIVE_STATEMENT_KEYWORDS } from './lib/src/types/directiveKeywords' - -interface FileContent { - filePath: string - fileContent: string[] -} - -export interface SymbolContent { - symbolName: string - startPosition: number - endPostion: number - filePath?: string - lineNumber?: number -} - -export class SymbolScanner { - private readonly _fileContent: FileContent[] = new Array < FileContent >() - private readonly _definitionProvider: DefinitionProvider - private readonly _symbolsDefinition: SymbolContent[] = new Array < SymbolContent >() - - constructor (fileUrlAsString: string, definitionProvider: DefinitionProvider) { - logger.debug(`scan for symbols file: ${fileUrlAsString}`) - - this._definitionProvider = definitionProvider - - this.extendsFile(this.convertUriStringToFilePath(fileUrlAsString)) - this.scanForSymbols() - } - - get symbols (): SymbolContent[] { - return this._symbolsDefinition - } - - private extendsFile (filePath: string): void { - logger.debug(`extendsFile file: ${filePath}`) - - try { - const data: Buffer = fs.readFileSync(filePath) - const file: string[] = data.toString().split(/\r?\n/g) - - this._fileContent.push({ - filePath, - fileContent: file - }) - - for (const line of file) { - const words = line.split(' ') - - if (new Set(DIRECTIVE_STATEMENT_KEYWORDS).has(words[0])) { - logger.debug(`Directive statement keyword found: ${words[0]}`) - this.handleKeyword(words[0], line) - } - } - } catch (error) { - if (error instanceof Error) { // Check if error is an instance of the native JavaScript Error class - logger.error(`Error reading file at ${filePath}: ${error.message}`) - } else if (typeof error === 'string') { - logger.error(`Error reading file at ${filePath}: ${error}`) - } else { - logger.error(`An unknown error occurred while reading the file at ${filePath}`) - } - } - } - - private handleKeyword (keyword: string, line: string): void { - const restOfLine: string[] = line.split(keyword).filter(String) - - if (restOfLine.length === 1) { - const listOfSymbols: string[] = restOfLine[0].split(' ').filter(String) - let definition: Definition = new Array < Location >() - - if (listOfSymbols.length === 1) { - definition = definition.concat(this._definitionProvider.createDefinitionForKeyword(keyword, restOfLine[0])) - } else if (listOfSymbols.length > 1) { - for (const symbol of listOfSymbols) { - definition = definition.concat(this._definitionProvider.createDefinitionForKeyword(keyword, restOfLine[0], symbol)) - } - } - - for (const location of definition) { - if (location !== null) { - this.extendsFile(this.convertUriStringToFilePath(location.uri)) - } - } - } - } - - private convertUriStringToFilePath (fileUrlAsString: string): string { - const fileUrl = new URL(fileUrlAsString) - // Use decodeURIComponent to properly decode each part of the URL - // This correctly decodes url in Windows such as %3A -> : - let filePath: string = decodeURIComponent(fileUrl.pathname) - - // For Windows, remove the leading slash if it exists - if (process.platform === 'win32' && filePath.startsWith('/')) { - filePath = filePath.substring(1) - } - - return filePath - } - - private scanForSymbols (): void { - for (const file of this._fileContent) { - for (const line of file.fileContent) { - const lineIndex: number = file.fileContent.indexOf(line) - const regex = /^\s*(?:export)?\s*(\w*(?:\[\w*\])?)\s*(?:=|:=|\+=|=\+|-=|=-|\?=|\?\?=|\.=|=\.)/g - const symbolContent = this.investigateLine(line, regex) - - if (symbolContent !== undefined) { - symbolContent.filePath = file.filePath - symbolContent.lineNumber = lineIndex - - this._symbolsDefinition.push(symbolContent) - } - } - } - } - - private investigateLine (lineString: string, regex: RegExp): SymbolContent | undefined { - let m - - while ((m = regex.exec(lineString)) !== null) { - // This is necessary to avoid infinite loops with zero-width matches - if (m.index === regex.lastIndex) { - regex.lastIndex++ - } - - if (m.length === 2) { - const symbol: string = m[1] - const filterdSymbolName = this.filterSymbolName(symbol) - if (filterdSymbolName === undefined) { - return undefined - } - const symbolStartPosition: number = lineString.indexOf(symbol) - const symbolEndPosition: number = symbolStartPosition + symbol.length - - return { - symbolName: filterdSymbolName, - startPosition: symbolStartPosition, - endPostion: symbolEndPosition - } - } - } - - return undefined - } - - private filterSymbolName (symbol: string): string | undefined { - const regex = /^\w*/g - let m - let filterdSymbolName: string | undefined - - while ((m = regex.exec(symbol)) !== null) { - // This is necessary to avoid infinite loops with zero-width matches - if (m.index === regex.lastIndex) { - regex.lastIndex++ - } - - filterdSymbolName = m[0] - } - - return filterdSymbolName - } -} diff --git a/server/src/connectionHandlers/onDefinition.ts b/server/src/connectionHandlers/onDefinition.ts index 178d49cd..9cf3380d 100644 --- a/server/src/connectionHandlers/onDefinition.ts +++ b/server/src/connectionHandlers/onDefinition.ts @@ -4,10 +4,12 @@ * ------------------------------------------------------------------------------------------ */ import { logger } from '../lib/src/utils/OutputLogger' -import { type TextDocumentPositionParams, type Definition } from 'vscode-languageserver/node' +import { type TextDocumentPositionParams, type Definition, Location, Range } from 'vscode-languageserver/node' import { analyzer } from '../tree-sitter/analyzer' import { type DirectiveStatementKeyword } from '../lib/src/types/directiveKeywords' -import { definitionProvider } from '../DefinitionProvider' +import { bitBakeProjectScannerClient } from '../BitbakeProjectScannerClient' +import path, { type ParsedPath } from 'path' +import { type PathInfo, type ElementInfo } from '../lib/src/types/BitbakeScanResult' export function onDefinitionHandler (textDocumentPositionParams: TextDocumentPositionParams): Definition | null { const { textDocument: { uri: documentUri }, position } = textDocumentPositionParams @@ -31,9 +33,10 @@ export function onDefinitionHandler (textDocumentPositionParams: TextDocumentPos // require, inherit & include directives const directiveStatementKeyword = analyzer.getDirectiveStatementKeywordByNodeType(textDocumentPositionParams) - if (directiveStatementKeyword !== undefined) { + const directivePath = analyzer.getDirectivePathForPosition(textDocumentPositionParams) + if (directiveStatementKeyword !== undefined && directivePath !== undefined) { logger.debug(`[onDefinition] Found directive: ${directiveStatementKeyword}`) - const definition = getDefinitionForDirectives(directiveStatementKeyword, textDocumentPositionParams, documentAsText) + const definition = getDefinitionForDirectives(directiveStatementKeyword, directivePath) logger.debug(`[onDefinition] definition item: ${JSON.stringify(definition)}`) return definition } @@ -57,33 +60,103 @@ export function onDefinitionHandler (textDocumentPositionParams: TextDocumentPos return getDefinition(textDocumentPositionParams, documentAsText) } -function getDefinitionForDirectives (directiveStatementKeyword: DirectiveStatementKeyword, textDocumentPositionParams: TextDocumentPositionParams, documentAsText: string[]): Definition { - let definition: Definition = [] - const currentLine: string = documentAsText[textDocumentPositionParams.position.line] - const symbol: string = extractSymbolFromLine(textDocumentPositionParams, currentLine) - - const words: string[] = currentLine.split(' ') - - if (words.length >= 2) { - if (words[0] === directiveStatementKeyword) { - logger.debug(`getDefinitionForKeyWord: ${JSON.stringify(words)}`) - if (words.length === 2) { - definition = definitionProvider.createDefinitionForKeyword(directiveStatementKeyword, words[1]) - } else { - definition = definitionProvider.createDefinitionForKeyword(directiveStatementKeyword, words[1], symbol) +function getDefinitionForDirectives (directiveStatementKeyword: DirectiveStatementKeyword, symbol: string): Definition { + let elementInfos: ElementInfo[] = [] + switch (directiveStatementKeyword) { + case 'inherit': + elementInfos = bitBakeProjectScannerClient.bitbakeScanResult._classes.filter((bbclass): boolean => { + return bbclass.name === symbol + }) + break + + case 'require': + case 'include': + { + const includeFile = path.parse(symbol) + elementInfos = bitBakeProjectScannerClient.bitbakeScanResult._includes.filter((incFile): boolean => { + return incFile.name === includeFile.name + }) + + if (elementInfos.length === 0) { + elementInfos = bitBakeProjectScannerClient.bitbakeScanResult._recipes.filter((recipe): boolean => { + return recipe.name === includeFile.name + }) + } } + break + + default: + return [] + } + + const definition: Definition = [] + for (const elementInfo of elementInfos) { + if (elementInfo.path !== undefined) { + const location: Location = createDefinitionLocationForPathInfo(elementInfo.path) + definition.push(location) } } return definition } +function createDefinitionLocationForPathInfo (path: ParsedPath): Location { + const url: string = 'file://' + path.dir + '/' + path.base + const location: Location = Location.create(encodeURI(url), Range.create(0, 0, 0, 0)) + + return location +} + function getDefinition (textDocumentPositionParams: TextDocumentPositionParams, documentAsText: string[]): Definition { let definition: Definition = [] const currentLine = documentAsText[textDocumentPositionParams.position.line] const symbol = extractSymbolFromLine(textDocumentPositionParams, currentLine) - definition = definitionProvider.createDefinitionForSymbol(symbol) + definition = createDefinitionForSymbol(symbol) + return definition +} + +function createDefinitionForSymbol (symbol: string): Definition { + return createDefinitionForSymbolRecipes(symbol) +} + +function createDefinitionForSymbolRecipes (symbol: string): Definition { + let definitions: Definition = [] + + const recipe: ElementInfo | undefined = bitBakeProjectScannerClient.bitbakeScanResult._recipes.find((obj: ElementInfo): boolean => { + return obj.name === symbol + }) + + if (recipe?.path !== undefined) { + let definitionsList: PathInfo[] = new Array < PathInfo >(recipe.path) + + if ((recipe.appends !== undefined) && (recipe.appends.length > 0)) { + definitionsList = definitionsList.concat(recipe.appends) + } + definitions = createDefinitionLocationForPathInfoList(definitionsList) + } + + return definitions +} + +function createDefinitionLocationForPathInfoList (pathInfoList: PathInfo[]): Definition { + let definition: Definition = [] + + if ((pathInfoList !== undefined) && (pathInfoList.length > 0)) { + if (pathInfoList.length > 1) { + definition = new Array < Location >() + + for (const pathInfo of pathInfoList) { + logger.debug(`definition ${JSON.stringify(pathInfo)}`) + const location: Location = createDefinitionLocationForPathInfo(pathInfo) + + definition.push(location) + } + } else { + definition = createDefinitionLocationForPathInfo(pathInfoList[0]) + } + } + return definition } diff --git a/server/src/server.ts b/server/src/server.ts index 0ee7d9a5..83f4aacb 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -16,7 +16,6 @@ import { FileChangeType } from 'vscode-languageserver/node' import { bitBakeDocScanner } from './BitBakeDocScanner' -import { SymbolScanner } from './SymbolScanner' import { TextDocument } from 'vscode-languageserver-textdocument' import { analyzer } from './tree-sitter/analyzer' import { generateParser } from './tree-sitter/parser' @@ -29,7 +28,6 @@ import { embeddedLanguageDocsManager } from './embedded-languages/documents-mana import { RequestMethod, type RequestParams, type RequestResult } from './lib/src/types/requests' import { NotificationMethod, type NotificationParams } from './lib/src/types/notifications' import { getSemanticTokens, legend } from './semanticTokens' -import { definitionProvider } from './DefinitionProvider' import { bitBakeProjectScannerClient } from './BitbakeProjectScannerClient' // Create a connection for the server. The connection uses Node's IPC as a transport @@ -131,8 +129,6 @@ connection.listen() documents.onDidChangeContent(async (event) => { const textDocument = event.document - logger.debug('[onDidChangeContent] Set new symbol scanner') - definitionProvider.symbolScanner = new SymbolScanner(textDocument.uri, definitionProvider) if (textDocument.getText().length > 0) { const diagnostics = await analyzer.analyze({ document: textDocument, uri: textDocument.uri }) @@ -147,11 +143,6 @@ documents.onDidChangeContent(async (event) => { } }) -documents.onDidClose((event) => { - logger.debug('[onDidClose] Set symbol scanner to null') - definitionProvider.symbolScanner = null -}) - documents.onDidSave(async (event) => { if (parseOnSave) { logger.debug(`onDidSave ${JSON.stringify(event)}`) diff --git a/server/src/tree-sitter/analyzer.ts b/server/src/tree-sitter/analyzer.ts index 02b9a800..bc2438e2 100644 --- a/server/src/tree-sitter/analyzer.ts +++ b/server/src/tree-sitter/analyzer.ts @@ -198,6 +198,22 @@ export default class Analyzer { } } + public getDirectivePathForPosition ( + params: TextDocumentPositionParams + ): string | undefined { + const n = this.nodeAtPoint( + params.textDocument.uri, + params.position.line, + params.position.character + ) + + if (n?.type === 'inherit_path' || n?.type === 'include_path') { + return n.text + } + + return undefined + } + public isIdentifier ( params: TextDocumentPositionParams ): boolean {