diff --git a/server/src/analyzer.ts b/server/src/analyzer.ts index e4229a2..ef9086a 100644 --- a/server/src/analyzer.ts +++ b/server/src/analyzer.ts @@ -71,16 +71,30 @@ export default class Analyzer { * @param uri uri to the library root * @param isWorkspace `true` if this is a user workspace/project, `false` if * this is a library. + * @returns `true` if the library was loaded, `false` if it was skipped because + * the path does not exist or does not contain a `package.mo`. */ - public async loadLibrary(uri: LSP.URI, isWorkspace: boolean): Promise { + public async loadLibrary(uri: LSP.URI, isWorkspace: boolean): Promise { const isLibrary = (folderPath: string) => fsSync.existsSync(path.join(folderPath, 'package.mo')); const libraryPath = url.fileURLToPath(uri); + if (!fsSync.existsSync(libraryPath)) { + logger.debug( + `Skipping ${isWorkspace ? 'workspace' : 'library'} at '${libraryPath}': path does not exist.`, + ); + return false; + } + if (!isWorkspace || isLibrary(libraryPath)) { + if (!isLibrary(libraryPath)) { + logger.debug(`Skipping library at '${libraryPath}': no 'package.mo' found.`); + return false; + } + const lib = await ModelicaLibrary.load(this.#project, libraryPath, isWorkspace); this.#project.addLibrary(lib); - return; + return true; } // TODO: go deeper... something like `TreeSitterUtil.forEach` but for files @@ -94,6 +108,8 @@ export default class Analyzer { const library = await ModelicaLibrary.load(this.#project, nested, isWorkspace); this.#project.addLibrary(library); } + + return true; } /** diff --git a/server/src/server.ts b/server/src/server.ts index 7d3c220..b93885b 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -83,7 +83,13 @@ export class ModelicaServer { const analyzer = new Analyzer(parser); if (workspaceFolders != null) { for (const workspace of workspaceFolders) { - await analyzer.loadLibrary(workspace.uri, true); + try { + await analyzer.loadLibrary(workspace.uri, true); + } catch (err) { + logger.error( + `Failed to load workspace '${workspace.uri}': ${err instanceof Error ? err.message : err}`, + ); + } } } @@ -105,7 +111,20 @@ export class ModelicaServer { for (const libraryPath of configuredLibraries) { const libraryUri = url.pathToFileURL(path.resolve(libraryPath)).toString(); logger.debug(`Loading configured library '${libraryPath}'`); - await analyzer.loadLibrary(libraryUri, false); + try { + const loaded = await analyzer.loadLibrary(libraryUri, false); + if (!loaded) { + const message = + `Could not load Modelica library '${libraryPath}': the path does not exist or has no ` + + `'package.mo'. Remove or fix this entry in the "modelica.libraries" setting to stop loading it.`; + logger.warn(message); + connection.window.showWarningMessage(message); + } + } catch (err) { + logger.error( + `Failed to load configured library '${libraryPath}': ${err instanceof Error ? err.message : err}`, + ); + } } logger.debug('Initialized'); diff --git a/server/src/test/analyzer.test.ts b/server/src/test/analyzer.test.ts new file mode 100644 index 0000000..e7dd9d2 --- /dev/null +++ b/server/src/test/analyzer.test.ts @@ -0,0 +1,65 @@ +/* + * This file is part of OpenModelica. + * + * Copyright (c) 1998-2024, Open Source Modelica Consortium (OSMC), + * c/o Linköpings universitet, Department of Computer and Information Science, + * SE-58183 Linköping, Sweden. + * + * All rights reserved. + * + * THIS PROGRAM IS PROVIDED UNDER THE TERMS OF AGPL VERSION 3 LICENSE OR + * THIS OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.8. + * ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES + * RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GNU AGPL + * VERSION 3, ACCORDING TO RECIPIENTS CHOICE. + * + * The OpenModelica software and the OSMC (Open Source Modelica Consortium) + * Public License (OSMC-PL) are obtained from OSMC, either from the above + * address, from the URLs: + * http://www.openmodelica.org or + * https://github.com/OpenModelica/ or + * http://www.ida.liu.se/projects/OpenModelica, + * and in the OpenModelica distribution. + * + * GNU AGPL version 3 is obtained from: + * https://www.gnu.org/licenses/licenses.html#GPL + * + * This program is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY SET FORTH + * IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF OSMC-PL. + * + * See the full OSMC Public License conditions for more details. + * + */ + +import assert from 'node:assert/strict'; +import * as path from 'node:path'; +import * as url from 'node:url'; + +import Analyzer from '../analyzer'; +import { initializeParser } from '../parser'; + +describe('Analyzer.loadLibrary', () => { + let analyzer: Analyzer; + + beforeEach(async () => { + const parser = await initializeParser(); + analyzer = new Analyzer(parser); + }); + + // Regression test for https://github.com/OpenModelica/modelica-language-server/issues/49 + it('skips a configured library whose path does not exist without throwing', async () => { + const missingPath = path.join(__dirname, 'this-library-does-not-exist'); + const missingUri = url.pathToFileURL(missingPath).toString(); + + assert.equal(await analyzer.loadLibrary(missingUri, false), false); + }); + + it('skips a workspace whose path does not exist without throwing', async () => { + const missingPath = path.join(__dirname, 'this-workspace-does-not-exist'); + const missingUri = url.pathToFileURL(missingPath).toString(); + + assert.equal(await analyzer.loadLibrary(missingUri, true), false); + }); +});