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

Import from anywhere #604

Merged
merged 20 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4831d0c
Use JayveeService instead of Langium specific one in utils
georg-schwarz Jul 26, 2024
f60e4e7
Add method findUnresolvedImportURIs to JayveeImportResolver
georg-schwarz Jul 26, 2024
11849b5
Add hook on workspace initialization to load unresolved documents out…
georg-schwarz Jul 26, 2024
a79bbd3
Initialize the workspace in the VSCode extension
georg-schwarz Jul 26, 2024
f619fe4
Dynamic file loading from fs as string instead from URI to allow brow…
georg-schwarz Jul 26, 2024
641d3be
Use FileSystemProvider instead of node:fs
georg-schwarz Jul 26, 2024
b9abbaa
Add launch config for electric vehicle example
georg-schwarz Jul 26, 2024
675c795
Add hooks before initializing the workspace
georg-schwarz Jul 26, 2024
1da7dc2
Use mutex to prevent adding documents multiple times
georg-schwarz Jul 26, 2024
835f9ca
Only load documents with correct file extension
georg-schwarz Jul 26, 2024
9a93a06
Fix error message when import cannot be resolved
georg-schwarz Jul 26, 2024
2803955
Refactor check on correct file extension before loading
georg-schwarz Jul 26, 2024
d942134
Remove mutex due to deadlock
georg-schwarz Jul 26, 2024
4ce82e2
Add tests for the dynamic import
georg-schwarz Jul 26, 2024
74a5ae0
Place more narrow mutex lock around adding new documents
georg-schwarz Jul 29, 2024
25274d6
Rename std-lib directory to workspace
georg-schwarz Jul 29, 2024
86a66f7
Extract dynamic file import to own file and initialize while creating…
georg-schwarz Jul 29, 2024
4d4b10c
Add license text
georg-schwarz Jul 29, 2024
add26e4
Fix copyright year
georg-schwarz Jul 30, 2024
77c608c
Fix typo in test file name
georg-schwarz Jul 30, 2024
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
4 changes: 2 additions & 2 deletions apps/docs/docs/dev/04-guides/05-standard-library.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ We use code generation to transform these `.jv` files into TypeScript files that

### 2. Builtin libraries

The solution we chose to implement the standard library mechanism is close to the [built-in library tutorial](https://langium.org/guides/builtin-library/) by Langium. The following components are of interest:
The solution we chose to implement the standard library mechanism is close to the [built-in library tutorial](https://langium.org/guides/workspace/) by Langium. The following components are of interest:

- [JayveeWorkspaceManager](https://github.com/jvalue/jayvee/tree/main/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts) in the `language-server` that registers all libraries with the langium framework.
- [JayveeWorkspaceManager](https://github.com/jvalue/jayvee/tree/main/libs/language-server/src/lib/workspace/jayvee-workspace-manager.ts) in the `language-server` that registers all libraries with the langium framework.
- [StandardLibraryFileSystemProvider](https://github.com/jvalue/jayvee/tree/main/apps/vs-code-extension/src/standard-library-file-system-provider.ts) in the `vs-code-extension` that registers all libraries with the vscode plugin framework.
99 changes: 4 additions & 95 deletions apps/vs-code-extension/src/language-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,109 +2,18 @@
//
// SPDX-License-Identifier: AGPL-3.0-only

import {
type JayveeServices,
createJayveeServices,
initializeWorkspace,
} from '@jvalue/jayvee-language-server';
import {
type LangiumSharedServices,
addCallHierarchyHandler,
addCodeActionHandler,
addCodeLensHandler,
addCompletionHandler,
addConfigurationChangeHandler,
addDiagnosticsHandler,
addDocumentHighlightsHandler,
addDocumentLinkHandler,
addDocumentSymbolHandler,
addDocumentUpdateHandler,
addExecuteCommandHandler,
addFileOperationHandler,
addFindReferencesHandler,
addFoldingRangeHandler,
addFormattingHandler,
addGoToDeclarationHandler,
addGoToImplementationHandler,
addGoToTypeDefinitionHandler,
addGotoDefinitionHandler,
addHoverHandler,
addInlayHintHandler,
addRenameHandler,
addSemanticTokenHandler,
addSignatureHelpHandler,
addTypeHierarchyHandler,
addWorkspaceSymbolHandler,
} from 'langium/lsp';
import { createJayveeServices } from '@jvalue/jayvee-language-server';
import { startLanguageServer } from 'langium/lsp';
import { NodeFileSystem } from 'langium/node';
import { ProposedFeatures, createConnection } from 'vscode-languageserver/node';

// Create a connection to the client
const connection = createConnection(ProposedFeatures.all);

// Inject the shared services and language-specific services
const services = createJayveeServices({
const { shared } = createJayveeServices({
connection,
...NodeFileSystem,
});

startLanguageServer(services.shared, services.Jayvee);

/**
* Starts the language server and registers hooks.
* Adapted from 'langium/lsp' to initialize workspace correctly.
*/
function startLanguageServer(
shared: LangiumSharedServices,
jayvee: JayveeServices,
): void {
const connection = shared.lsp.Connection;
if (!connection) {
throw new Error(
'Starting a language server requires the languageServer.Connection service to be set.',
);
}

addDocumentUpdateHandler(connection, shared);
addFileOperationHandler(connection, shared);
addDiagnosticsHandler(connection, shared);
addCompletionHandler(connection, shared);
addFindReferencesHandler(connection, shared);
addDocumentSymbolHandler(connection, shared);
addGotoDefinitionHandler(connection, shared);
addGoToTypeDefinitionHandler(connection, shared);
addGoToImplementationHandler(connection, shared);
addDocumentHighlightsHandler(connection, shared);
addFoldingRangeHandler(connection, shared);
addFormattingHandler(connection, shared);
addCodeActionHandler(connection, shared);
addRenameHandler(connection, shared);
addHoverHandler(connection, shared);
addInlayHintHandler(connection, shared);
addSemanticTokenHandler(connection, shared);
addExecuteCommandHandler(connection, shared);
addSignatureHelpHandler(connection, shared);
addCallHierarchyHandler(connection, shared);
addTypeHierarchyHandler(connection, shared);
addCodeLensHandler(connection, shared);
addDocumentLinkHandler(connection, shared);
addConfigurationChangeHandler(connection, shared);
addGoToDeclarationHandler(connection, shared);
addWorkspaceSymbolHandler(connection, shared);

connection.onInitialize(async (params) => {
const initResult = shared.lsp.LanguageServer.initialize(params);
await initializeWorkspace(jayvee, []); // Initialize workspace to register dynamic document loading hooks
return initResult;
});
connection.onInitialized((params) => {
shared.lsp.LanguageServer.initialized(params);
});

// Make the text document manager listen on the connection for open, change and close text document events.
const documents = shared.workspace.TextDocuments;
documents.listen(connection);

// Start listening for incoming messages from the client.
connection.listen();
}
startLanguageServer(shared);
2 changes: 1 addition & 1 deletion libs/language-server/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

# files generated by Langium
src/lib/ast/generated
src/lib/builtin-library/generated
src/lib/workspace/generated

This file was deleted.

2 changes: 1 addition & 1 deletion libs/language-server/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0-only

export * from './ast';
export * from './builtin-library';
export * from './workspace';
export * from './docs';
export * from './services';
export * from './util';
Expand Down
5 changes: 4 additions & 1 deletion libs/language-server/src/lib/jayvee-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import {
} from './ast/generated/module';
import { ValueTypeProvider } from './ast/wrappers/value-type/primitive/primitive-value-type-provider';
import { WrapperFactoryProvider } from './ast/wrappers/wrapper-factory-provider';
import { JayveeWorkspaceManager } from './builtin-library/jayvee-workspace-manager';
import { JayveeValueConverter } from './jayvee-value-converter';
import {
JayveeCodeActionProvider,
Expand All @@ -37,6 +36,8 @@ import {
import { JayveeImportResolver } from './services/import-resolver';
import { RuntimeParameterProvider } from './services/runtime-parameter-provider';
import { JayveeValidationRegistry } from './validation/validation-registry';
import { addDynamicFileImport } from './workspace';
import { JayveeWorkspaceManager } from './workspace/jayvee-workspace-manager';

/**
* Declaration of custom services for the Jayvee language.
Expand Down Expand Up @@ -150,5 +151,7 @@ export function createJayveeServices(context: DefaultSharedModuleContext): {
);
shared.ServiceRegistry.register(Jayvee);

addDynamicFileImport(Jayvee);

return { shared, Jayvee };
}
90 changes: 90 additions & 0 deletions libs/language-server/src/lib/lsp/import-dynamic-reference.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg
//
// SPDX-License-Identifier: AGPL-3.0-only

import path from 'node:path';

import { NodeFileSystem } from 'langium/node';

import {
type JayveeServices,
createJayveeServices,
isJayveeModel,
} from '../../lib';
import {
expectNoParserAndLexerErrors,
parseTestFileInWorkingDir,
} from '../../test';
import { type JayveeModel } from '../ast';

describe('References to imported elements outside of the working directory', () => {
const WORKING_DIR = path.resolve(
__dirname,
'../../test/assets/import-dynamic-reference/models', // use the "deep" directory as working dir to avoid loading the "higher" dir
);
let services: JayveeServices;

async function parseModel(
relativeTestFilePath: string,
): Promise<JayveeModel> {
const document = await parseTestFileInWorkingDir(
WORKING_DIR,
relativeTestFilePath,
services,
);
expectNoParserAndLexerErrors(document);

const parsedModel = document.parseResult.value;
assert(isJayveeModel(parsedModel), 'Test file is not valid Jayvee model');
return parsedModel;
}

beforeEach(() => {
// Create language services
services = createJayveeServices(NodeFileSystem).Jayvee;
});

const validCases: [string, string][] = [
// [test name, test file path]
[
'should resolve reference to imported element',
'valid-import-from-outside-workdir.jv',
],
];
test.each(validCases)('%s', async (_, relativeTestFilePath) => {
const model = await parseModel(relativeTestFilePath);

expect(model.pipelines.length).toEqual(1); // file contains one pipeline
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const blocks = model.pipelines[0]!.blocks;
expect(blocks.length).toEqual(1); // pipeline contains one block
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const reference = blocks[0]!.type; // of an imported block type

expect(reference.ref).toBeDefined();
});

const invalidCases: [string, string][] = [
// [test name, test file path]
[
'should not resolve reference to file with no jv extension',
'invalid-import-wrong-file-type.jv',
],
[
'should not resolve reference to file that does not exist',
'invalid-import-not-existing-file.jv',
],
];
test.each(invalidCases)('%s', async (_, relativeTestFilePath) => {
const model = await parseModel(relativeTestFilePath);

expect(model.pipelines.length).toEqual(1); // file contains one pipeline
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const blocks = model.pipelines[0]!.blocks;
expect(blocks.length).toEqual(1); // pipeline contains one block
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const reference = blocks[0]!.type; // of an imported block type

expect(reference.ref).toBeUndefined();
});
});
2 changes: 1 addition & 1 deletion libs/language-server/src/lib/services/import-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import {
isExportableElement,
isJayveeModel,
} from '../ast/generated/ast';
import { getStdLib } from '../builtin-library/stdlib';
import { type JayveeServices } from '../jayvee-module';
import { getStdLib } from '../workspace/stdlib';

export interface ImportDetails {
element: ExportableElement;
Expand Down
Loading
Loading