From ba1e9a8105dca21ddc155a1939bf1228bc008cad Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sun, 22 Oct 2023 14:49:39 +0200 Subject: [PATCH] feat: stop validation after lexing/parsing errors (#662) ### Summary of Changes Linking or custom validation errors are no longer displayed if a document has lexing or parsing errors. This better highlights the criticality of those issues and ensures that users fix those first. It also prevents the display of some additional errors that were caused by the lexing or parsing errors. --- src/language/helpers/safe-ds-node-mapper.ts | 18 +++++++++--------- src/language/safe-ds-module.ts | 2 ++ src/language/typing/safe-ds-class-hierarchy.ts | 4 ++-- .../workspace/safe-ds-document-builder.ts | 11 +++++++++++ .../builtins/builtinFilesCorrectness.test.ts | 8 ++++---- 5 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 src/language/workspace/safe-ds-document-builder.ts diff --git a/src/language/helpers/safe-ds-node-mapper.ts b/src/language/helpers/safe-ds-node-mapper.ts index 2ef2a0151..98cf7a8a2 100644 --- a/src/language/helpers/safe-ds-node-mapper.ts +++ b/src/language/helpers/safe-ds-node-mapper.ts @@ -29,15 +29,15 @@ import { SdsYield, } from '../generated/ast.js'; import { CallableType, StaticType } from '../typing/model.js'; -import { findLocalReferences, getContainerOfType, Stream, stream } from 'langium'; +import { EMPTY_STREAM, findLocalReferences, getContainerOfType, Stream } from 'langium'; import { getAbstractResults, getArguments, - isNamedArgument, - isNamedTypeArgument, getParameters, getTypeArguments, getTypeParameters, + isNamedArgument, + isNamedTypeArgument, } from './nodeProperties.js'; export class SafeDsNodeMapper { @@ -155,13 +155,13 @@ export class SafeDsNodeMapper { */ parameterToReferences(node: SdsParameter | undefined): Stream { if (!node) { - return stream(); + return EMPTY_STREAM; } const containingCallable = getContainerOfType(node, isSdsCallable); /* c8 ignore start */ if (!containingCallable) { - return stream(); + return EMPTY_STREAM; } /* c8 ignore stop */ @@ -175,13 +175,13 @@ export class SafeDsNodeMapper { */ placeholderToReferences(node: SdsPlaceholder | undefined): Stream { if (!node) { - return stream(); + return EMPTY_STREAM; } const containingBlock = getContainerOfType(node, isSdsBlock); /* c8 ignore start */ if (!containingBlock) { - return stream(); + return EMPTY_STREAM; } /* c8 ignore stop */ @@ -195,12 +195,12 @@ export class SafeDsNodeMapper { */ resultToYields(node: SdsResult | undefined): Stream { if (!node) { - return stream(); + return EMPTY_STREAM; } const containingSegment = getContainerOfType(node, isSdsSegment); if (!containingSegment) { - return stream(); + return EMPTY_STREAM; } return findLocalReferences(node, containingSegment) diff --git a/src/language/safe-ds-module.ts b/src/language/safe-ds-module.ts index 17c493b74..31912ff63 100644 --- a/src/language/safe-ds-module.ts +++ b/src/language/safe-ds-module.ts @@ -28,6 +28,7 @@ import { SafeDsTypeChecker } from './typing/safe-ds-type-checker.js'; import { SafeDsCoreTypes } from './typing/safe-ds-core-types.js'; import { SafeDsNodeKindProvider } from './lsp/safe-ds-node-kind-provider.js'; import { SafeDsDocumentSymbolProvider } from './lsp/safe-ds-document-symbol-provider.js'; +import { SafeDsDocumentBuilder } from './workspace/safe-ds-document-builder.js'; /** * Declaration of custom services - add your own service classes here. @@ -106,6 +107,7 @@ export const SafeDsSharedModule: Module new SafeDsNodeKindProvider(), }, workspace: { + DocumentBuilder: (services) => new SafeDsDocumentBuilder(services), WorkspaceManager: (services) => new SafeDsWorkspaceManager(services), }, }; diff --git a/src/language/typing/safe-ds-class-hierarchy.ts b/src/language/typing/safe-ds-class-hierarchy.ts index 479af0752..1bc6c7a87 100644 --- a/src/language/typing/safe-ds-class-hierarchy.ts +++ b/src/language/typing/safe-ds-class-hierarchy.ts @@ -1,7 +1,7 @@ import { SafeDsServices } from '../safe-ds-module.js'; import { SafeDsClasses } from '../builtins/safe-ds-classes.js'; import { SdsClass } from '../generated/ast.js'; -import { stream, Stream } from 'langium'; +import { EMPTY_STREAM, stream, Stream } from 'langium'; import { getParentTypes } from '../helpers/nodeProperties.js'; import { SafeDsTypeComputer } from './safe-ds-type-computer.js'; import { ClassType } from './model.js'; @@ -39,7 +39,7 @@ export class SafeDsClassHierarchy { */ streamSuperclasses(node: SdsClass | undefined): Stream { if (!node) { - return stream(); + return EMPTY_STREAM; } return stream(this.superclassesGenerator(node)); diff --git a/src/language/workspace/safe-ds-document-builder.ts b/src/language/workspace/safe-ds-document-builder.ts new file mode 100644 index 000000000..ddef9fb9e --- /dev/null +++ b/src/language/workspace/safe-ds-document-builder.ts @@ -0,0 +1,11 @@ +import { BuildOptions, DefaultDocumentBuilder } from 'langium'; + +export class SafeDsDocumentBuilder extends DefaultDocumentBuilder { + override updateBuildOptions: BuildOptions = { + validation: { + categories: ['built-in', 'fast'], + stopAfterLexingErrors: true, + stopAfterParsingErrors: true, + }, + }; +} diff --git a/tests/language/builtins/builtinFilesCorrectness.test.ts b/tests/language/builtins/builtinFilesCorrectness.test.ts index 6a9d59371..81e434905 100644 --- a/tests/language/builtins/builtinFilesCorrectness.test.ts +++ b/tests/language/builtins/builtinFilesCorrectness.test.ts @@ -8,14 +8,14 @@ import { URI } from 'langium'; import { locationToString } from '../../helpers/location.js'; import { AssertionError } from 'assert'; import { isEmpty } from '../../../src/helpers/collectionUtils.js'; +import { loadDocuments } from '../../helpers/testResources.js'; -const workspace = createSafeDsServices(NodeFileSystem).SafeDs.shared.workspace; +const services = createSafeDsServices(NodeFileSystem).SafeDs; const builtinFiles = listBuiltinFiles(); describe('builtin files', () => { beforeAll(async () => { - const documents = builtinFiles.map((uri) => workspace.LangiumDocuments.getOrCreateDocument(uri)); - await workspace.DocumentBuilder.build(documents, { validation: true }); + await loadDocuments(services, builtinFiles, { validation: true }); }); const testCases = builtinFiles.map((uri) => ({ @@ -23,7 +23,7 @@ describe('builtin files', () => { shortenedResourceName: uriToShortenedResourceName(uri, 'builtins'), })); it.each(testCases)('[$shortenedResourceName] should have no errors or warnings', async ({ uri }) => { - const document = workspace.LangiumDocuments.getOrCreateDocument(uri); + const document = services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri); const errorsOrWarnings = document.diagnostics?.filter(