From a048395ef1151f4c2651ac6cfd4f5fb55b8d3784 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Tue, 19 Dec 2023 16:42:43 +0100 Subject: [PATCH 01/14] Replace model-util helper methods with PipelineWrapper --- .../src/lib/blocks/block-execution-util.ts | 30 +++-- .../lib/blocks/composite-block-executor.ts | 11 +- libs/interpreter-lib/src/interpreter.ts | 19 ++- .../language-server/src/lib/ast/model-util.ts | 118 +++--------------- .../src/lib/ast/wrappers/index.ts | 3 + .../src/lib/ast/wrappers/pipeline-wrapper.ts | 74 +++++++++++ .../lib/validation/checks/block-definition.ts | 10 +- .../validation/checks/pipeline-definition.ts | 5 +- .../pipeline-definition/valid-pipeline.jv | 11 +- 9 files changed, 139 insertions(+), 142 deletions(-) create mode 100644 libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts diff --git a/libs/execution/src/lib/blocks/block-execution-util.ts b/libs/execution/src/lib/blocks/block-execution-util.ts index ed17c157b..dc98b4c1b 100644 --- a/libs/execution/src/lib/blocks/block-execution-util.ts +++ b/libs/execution/src/lib/blocks/block-execution-util.ts @@ -4,7 +4,10 @@ import { BlockDefinition, - collectParents, + CompositeBlocktypeDefinition, + PipelineDefinition, + PipelineWrapper, + getBlocksInTopologicalSorting, } from '@jvalue/jayvee-language-server'; import { ExecutionContext } from '../execution-context'; @@ -31,18 +34,31 @@ export interface ExecutionOrderItem { */ export async function executeBlocks( executionContext: ExecutionContext, - executionOrder: ExecutionOrderItem[], + compositeBlockTypeDefinition: + | CompositeBlocktypeDefinition + | PipelineDefinition, initialInputValue: IOTypeImplementation | undefined = undefined, ): Promise> { + const pipelineWrapper = new PipelineWrapper(compositeBlockTypeDefinition); + const executionOrder: { + block: BlockDefinition; + value: IOTypeImplementation | null; + }[] = getBlocksInTopologicalSorting(compositeBlockTypeDefinition).map( + (block) => { + return { block: block, value: NONE }; + }, + ); + let isFirstBlock = true; for (const blockData of executionOrder) { const block = blockData.block; - const parentData = collectParents(block).map((parent) => - executionOrder.find((blockData) => parent === blockData.block), - ); - let inputValue = - parentData[0]?.value === undefined ? NONE : parentData[0]?.value; + const parentData = pipelineWrapper + .getPredecessorBlocks(block) + .map((parent) => + executionOrder.find((blockData) => parent === blockData.block), + ); + let inputValue = parentData[0]?.value ?? NONE; const useExternalInputValueForFirstBlock = isFirstBlock && inputValue === NONE && initialInputValue !== undefined; diff --git a/libs/execution/src/lib/blocks/composite-block-executor.ts b/libs/execution/src/lib/blocks/composite-block-executor.ts index 8209eb015..f2fcf637c 100644 --- a/libs/execution/src/lib/blocks/composite-block-executor.ts +++ b/libs/execution/src/lib/blocks/composite-block-executor.ts @@ -16,13 +16,12 @@ import { createValuetype, evaluateExpression, evaluatePropertyValue, - getBlocksInTopologicalSorting, getIOType, isCompositeBlocktypeDefinition, } from '@jvalue/jayvee-language-server'; import { ExecutionContext } from '../execution-context'; -import { IOTypeImplementation, NONE } from '../types'; +import { IOTypeImplementation } from '../types'; // eslint-disable-next-line import/no-cycle import { executeBlocks } from './block-execution-util'; @@ -69,15 +68,9 @@ export function createCompositeBlockExecutor( this.addVariablesToContext(block, blockTypeReference.properties, context); - const executionOrder = getBlocksInTopologicalSorting( - blockTypeReference, - ).map((block) => { - return { block: block, value: NONE }; - }); - const executionResult = await executeBlocks( context, - executionOrder, + blockTypeReference, input, ); diff --git a/libs/interpreter-lib/src/interpreter.ts b/libs/interpreter-lib/src/interpreter.ts index 7fe7ac508..1234415bf 100644 --- a/libs/interpreter-lib/src/interpreter.ts +++ b/libs/interpreter-lib/src/interpreter.ts @@ -9,7 +9,6 @@ import { DebugGranularity, ExecutionContext, Logger, - NONE, executeBlocks, isDebugGranularity, logExecutionDuration, @@ -24,11 +23,9 @@ import { JayveeModel, JayveeServices, PipelineDefinition, + PipelineWrapper, RuntimeParameterProvider, - collectChildren, - collectStartingBlocks, createJayveeServices, - getBlocksInTopologicalSorting, initializeWorkspace, } from '@jvalue/jayvee-language-server'; import * as chalk from 'chalk'; @@ -228,12 +225,7 @@ async function runPipeline( const startTime = new Date(); - const executionOrder = getBlocksInTopologicalSorting(pipeline).map( - (block) => { - return { block: block, value: NONE }; - }, - ); - const executionResult = await executeBlocks(executionContext, executionOrder); + const executionResult = await executeBlocks(executionContext, pipeline); if (R.isErr(executionResult)) { const diagnosticError = executionResult.left; @@ -254,13 +246,16 @@ export function logPipelineOverview( runtimeParameterProvider: RuntimeParameterProvider, logger: Logger, ) { + const pipelineWrapper = new PipelineWrapper(pipeline); + const toString = (block: BlockDefinition, depth = 0): string => { const blockTypeName = block.type.ref?.name; assert(blockTypeName !== undefined); const blockString = `${'\t'.repeat(depth)} -> ${ block.name } (${blockTypeName})`; - const childString = collectChildren(block) + const childString = pipelineWrapper + .getSuccessorBlocks(block) .map((child) => toString(child, depth + 1)) .join('\n'); return blockString + '\n' + childString; @@ -280,7 +275,7 @@ export function logPipelineOverview( linesBuffer.push( `\tBlocks (${pipeline.blocks.length} blocks with ${pipeline.pipes.length} pipes):`, ); - for (const block of collectStartingBlocks(pipeline)) { + for (const block of pipelineWrapper.getStartingBlocks()) { linesBuffer.push(toString(block, 1)); } logger.logInfo(linesBuffer.join('\n')); diff --git a/libs/language-server/src/lib/ast/model-util.ts b/libs/language-server/src/lib/ast/model-util.ts index 06bfcd544..1fe2617cb 100644 --- a/libs/language-server/src/lib/ast/model-util.ts +++ b/libs/language-server/src/lib/ast/model-util.ts @@ -4,12 +4,7 @@ import { strict as assert } from 'assert'; -import { - AstNode, - LangiumDocuments, - Reference, - assertUnreachable, -} from 'langium'; +import { AstNode, LangiumDocuments } from 'langium'; import { BinaryExpression, @@ -20,101 +15,14 @@ import { PipelineDefinition, UnaryExpression, isBuiltinBlocktypeDefinition, - isCompositeBlocktypeDefinition, isJayveeModel, } from './generated/ast'; // eslint-disable-next-line import/no-cycle -import { BlockTypeWrapper, ConstraintTypeWrapper } from './wrappers'; import { - PipeWrapper, - createWrappersFromPipeChain, -} from './wrappers/pipe-wrapper'; - -export function collectStartingBlocks( - container: PipelineDefinition | CompositeBlocktypeDefinition, -): BlockDefinition[] { - // For composite blocks the first blocks of all pipelines are starting blocks as they have inputs - if (isCompositeBlocktypeDefinition(container)) { - const startingBlocks = container.pipes - .map((pipe) => pipe.blocks[0]) - .map((blockRef: Reference | undefined) => { - if ( - blockRef?.ref !== undefined && - BlockTypeWrapper.canBeWrapped(blockRef.ref.type) - ) { - return blockRef.ref; - } - return undefined; - }) - .filter((x): x is BlockDefinition => x !== undefined); - - return startingBlocks; - } - - const result: BlockDefinition[] = []; - - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - const blocks = container?.blocks ?? []; - for (const block of blocks) { - if (!BlockTypeWrapper.canBeWrapped(block.type)) { - continue; - } - const blockType = new BlockTypeWrapper(block.type); - - if (!blockType.hasInput()) { - result.push(block); - } - } - return result; -} - -export function collectChildren(block: BlockDefinition): BlockDefinition[] { - const outgoingPipes = collectOutgoingPipes(block); - return outgoingPipes.map((pipe) => pipe.to); -} - -export function collectParents(block: BlockDefinition): BlockDefinition[] { - const ingoingPipes = collectIngoingPipes(block); - return ingoingPipes.map((pipe) => pipe.from); -} - -export function collectOutgoingPipes(block: BlockDefinition) { - return collectPipes(block, 'outgoing'); -} - -export function collectIngoingPipes(block: BlockDefinition) { - return collectPipes(block, 'ingoing'); -} - -function collectPipes( - block: BlockDefinition, - kind: 'outgoing' | 'ingoing', -): PipeWrapper[] { - const pipeline = block.$container; - const allPipes = collectAllPipes(pipeline); - - return allPipes.filter((pipeWrapper) => { - switch (kind) { - case 'outgoing': - return pipeWrapper.from === block; - case 'ingoing': - return pipeWrapper.to === block; - case undefined: - return false; - } - return assertUnreachable(kind); - }); -} - -export function collectAllPipes( - container: PipelineDefinition | CompositeBlocktypeDefinition, -): PipeWrapper[] { - const result: PipeWrapper[] = []; - for (const pipe of container.pipes) { - result.push(...createWrappersFromPipeChain(pipe)); - } - return result; -} + BlockTypeWrapper, + ConstraintTypeWrapper, + PipelineWrapper, +} from './wrappers'; /** * Returns blocks in a pipeline in topological order, based on @@ -134,9 +42,10 @@ export function collectAllPipes( export function getBlocksInTopologicalSorting( pipeline: PipelineDefinition | CompositeBlocktypeDefinition, ): BlockDefinition[] { + const pipelineWrapper = new PipelineWrapper(pipeline); const sortedNodes = []; - const currentNodes = [...collectStartingBlocks(pipeline)]; - let unvisitedEdges = [...collectAllPipes(pipeline)]; + const currentNodes = [...pipelineWrapper.getStartingBlocks()]; + let unvisitedEdges = [...pipelineWrapper.allPipes]; while (currentNodes.length > 0) { const node = currentNodes.pop(); @@ -144,18 +53,19 @@ export function getBlocksInTopologicalSorting( sortedNodes.push(node); - for (const childNode of collectChildren(node)) { + for (const childNode of pipelineWrapper.getSuccessorBlocks(node)) { // Mark edges between parent and child as visited - collectIngoingPipes(childNode) + pipelineWrapper + .getPredecessorPipes(childNode) .filter((e) => e.from === node) .forEach((e) => { unvisitedEdges = unvisitedEdges.filter((edge) => !edge.equals(e)); }); // If all edges to the child have been visited - const notRemovedEdges = collectIngoingPipes(childNode).filter((e) => - unvisitedEdges.some((edge) => edge.equals(e)), - ); + const notRemovedEdges = pipelineWrapper + .getPredecessorPipes(childNode) + .filter((e) => unvisitedEdges.some((edge) => edge.equals(e))); if (notRemovedEdges.length === 0) { // Insert it into currentBlocks currentNodes.push(childNode); diff --git a/libs/language-server/src/lib/ast/wrappers/index.ts b/libs/language-server/src/lib/ast/wrappers/index.ts index d757e8cda..81abbe4f1 100644 --- a/libs/language-server/src/lib/ast/wrappers/index.ts +++ b/libs/language-server/src/lib/ast/wrappers/index.ts @@ -10,3 +10,6 @@ export * from './ast-node-wrapper'; export * from './cell-range-wrapper'; export * from './typed-object'; + +export * from './pipe-wrapper'; +export * from './pipeline-wrapper'; diff --git a/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts b/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts new file mode 100644 index 000000000..688355bec --- /dev/null +++ b/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts @@ -0,0 +1,74 @@ +import { + BlockDefinition, + CompositeBlocktypeDefinition, + PipelineDefinition, +} from '../generated/ast'; + +import { AstNodeWrapper } from './ast-node-wrapper'; +import { PipeWrapper, createWrappersFromPipeChain } from './pipe-wrapper'; + +export class PipelineWrapper + implements AstNodeWrapper +{ + public readonly astNode: PipelineDefinition | CompositeBlocktypeDefinition; + + allPipes: PipeWrapper[] = []; + + constructor( + pipelineDefinition: PipelineDefinition | CompositeBlocktypeDefinition, + ) { + this.astNode = pipelineDefinition; + + this.allPipes = pipelineDefinition.pipes.flatMap((pipe) => + createWrappersFromPipeChain(pipe), + ); + } + + static canBeWrapped(pipelineDefinition: PipelineDefinition): boolean { + for (const pipeDefinition of pipelineDefinition.pipes) { + for ( + let chainIndex = 0; + chainIndex < pipeDefinition.blocks.length - 1; + ++chainIndex + ) { + if (!PipeWrapper.canBeWrapped(pipeDefinition, chainIndex)) { + return false; + } + } + } + return true; + } + + getStartingBlockPipes(): PipeWrapper[] { + return this.allPipes.filter((pipe) => { + const fromBlock = pipe.from; + const isToOfOtherPipe = + this.allPipes.filter((p) => p.to === fromBlock).length > 0; + return !isToOfOtherPipe; + }); + } + + getStartingBlocks(): BlockDefinition[] { + return this.getStartingBlockPipes().map((p) => p.from); + } + + getSuccessorPipes(blockDefinition: BlockDefinition): PipeWrapper[] { + return this.allPipes.filter((pipe) => { + return pipe.from === blockDefinition; + }); + } + + getSuccessorBlocks(blockDefinition: BlockDefinition): BlockDefinition[] { + return this.getSuccessorPipes(blockDefinition).map((p) => p.to); + } + + getPredecessorPipes(blockDefinition: BlockDefinition): PipeWrapper[] { + return this.allPipes.filter((pipe) => { + return pipe.to === blockDefinition; + }); + } + + getPredecessorBlocks(blockDefinition: BlockDefinition): BlockDefinition[] { + return this.getPredecessorPipes(blockDefinition).map((p) => p.from); + } +} diff --git a/libs/language-server/src/lib/validation/checks/block-definition.ts b/libs/language-server/src/lib/validation/checks/block-definition.ts index 97d011ba9..a279af758 100644 --- a/libs/language-server/src/lib/validation/checks/block-definition.ts +++ b/libs/language-server/src/lib/validation/checks/block-definition.ts @@ -9,14 +9,11 @@ import { assertUnreachable } from 'langium'; +import { PipelineWrapper } from '../../ast'; import { BlockDefinition, isCompositeBlocktypeDefinition, } from '../../ast/generated/ast'; -import { - collectIngoingPipes, - collectOutgoingPipes, -} from '../../ast/model-util'; import { PipeWrapper } from '../../ast/wrappers/pipe-wrapper'; import { BlockTypeWrapper } from '../../ast/wrappers/typed-object/blocktype-wrapper'; import { ValidationContext } from '../validation-context'; @@ -100,14 +97,15 @@ function collectPipes( block: BlockDefinition, whatToCheck: 'input' | 'output', ): PipeWrapper[] { + const pipelineWrapper = new PipelineWrapper(block.$container); let pipes: PipeWrapper[]; switch (whatToCheck) { case 'input': { - pipes = collectIngoingPipes(block); + pipes = pipelineWrapper.getPredecessorPipes(block); break; } case 'output': { - pipes = collectOutgoingPipes(block); + pipes = pipelineWrapper.getSuccessorPipes(block); break; } default: { diff --git a/libs/language-server/src/lib/validation/checks/pipeline-definition.ts b/libs/language-server/src/lib/validation/checks/pipeline-definition.ts index c3260e748..35a2198ad 100644 --- a/libs/language-server/src/lib/validation/checks/pipeline-definition.ts +++ b/libs/language-server/src/lib/validation/checks/pipeline-definition.ts @@ -2,8 +2,8 @@ // // SPDX-License-Identifier: AGPL-3.0-only +import { PipelineWrapper } from '../../ast'; import { PipelineDefinition } from '../../ast/generated/ast'; -import { collectStartingBlocks } from '../../ast/model-util'; import { ValidationContext } from '../validation-context'; import { checkUniqueNames } from '../validation-util'; @@ -22,7 +22,8 @@ function checkStartingBlocks( pipeline: PipelineDefinition, context: ValidationContext, ): void { - const startingBlocks = collectStartingBlocks(pipeline); + const pipelineWrapper = new PipelineWrapper(pipeline); + const startingBlocks = pipelineWrapper.getStartingBlocks(); if (startingBlocks.length === 0) { context.accept( 'error', diff --git a/libs/language-server/src/test/assets/pipeline-definition/valid-pipeline.jv b/libs/language-server/src/test/assets/pipeline-definition/valid-pipeline.jv index 26af22d9e..c63c7e306 100644 --- a/libs/language-server/src/test/assets/pipeline-definition/valid-pipeline.jv +++ b/libs/language-server/src/test/assets/pipeline-definition/valid-pipeline.jv @@ -3,11 +3,18 @@ // SPDX-License-Identifier: AGPL-3.0-only pipeline Pipeline { - block TestExtractor oftype TestFileExtractor { - } + TestExtractor -> TestLoader; + + block TestExtractor oftype TestFileExtractor {} + block TestLoader oftype TestFileLoader {} } builtin blocktype TestFileExtractor { input inPort oftype None; output outPort oftype File; +} + +builtin blocktype TestFileLoader { + input inPort oftype File; + output outPort oftype None; } \ No newline at end of file From 7831feb7684440e08660a6d99af3cf69936c0be2 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Tue, 19 Dec 2023 16:45:03 +0100 Subject: [PATCH 02/14] Add test for pipeline without pipes --- .../checks/pipeline-definition.spec.ts | 17 ++++++++++++++++- .../invalid-pipeline-only-blocks.jv | 12 ++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 libs/language-server/src/test/assets/pipeline-definition/invalid-pipeline-only-blocks.jv diff --git a/libs/language-server/src/lib/validation/checks/pipeline-definition.spec.ts b/libs/language-server/src/lib/validation/checks/pipeline-definition.spec.ts index 9b1dee65e..d4853ac08 100644 --- a/libs/language-server/src/lib/validation/checks/pipeline-definition.spec.ts +++ b/libs/language-server/src/lib/validation/checks/pipeline-definition.spec.ts @@ -63,7 +63,7 @@ describe('Validation of PipelineDefinition', () => { validationAcceptorMock.mockReset(); }); - it('should diagnose error on missing extractor block', async () => { + it('should diagnose error on missing starting block (no blocks)', async () => { const text = readJvTestAsset( 'pipeline-definition/invalid-empty-pipeline.jv', ); @@ -78,6 +78,21 @@ describe('Validation of PipelineDefinition', () => { ); }); + it('should diagnose error on missing starting block (no pipes)', async () => { + const text = readJvTestAsset( + 'pipeline-definition/invalid-pipeline-only-blocks.jv', + ); + + await parseAndValidatePipeline(text); + + expect(validationAcceptorMock).toHaveBeenCalledTimes(1); + expect(validationAcceptorMock).toHaveBeenCalledWith( + 'error', + `An extractor block is required for this pipeline`, + expect.any(Object), + ); + }); + it('should have no error on valid pipeline', async () => { const text = readJvTestAsset('pipeline-definition/valid-pipeline.jv'); diff --git a/libs/language-server/src/test/assets/pipeline-definition/invalid-pipeline-only-blocks.jv b/libs/language-server/src/test/assets/pipeline-definition/invalid-pipeline-only-blocks.jv new file mode 100644 index 000000000..3c6115bfd --- /dev/null +++ b/libs/language-server/src/test/assets/pipeline-definition/invalid-pipeline-only-blocks.jv @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block TestExtractor oftype TestFileExtractor {} +} + +builtin blocktype TestFileExtractor { + input inPort oftype None; + output outPort oftype File; +} \ No newline at end of file From 7be2e285a263788d5e74d5f532f304078e2e4231 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Tue, 19 Dec 2023 16:47:07 +0100 Subject: [PATCH 03/14] Rename methods of PipelineWrapper --- .../execution/src/lib/blocks/block-execution-util.ts | 2 +- libs/interpreter-lib/src/interpreter.ts | 2 +- libs/language-server/src/lib/ast/model-util.ts | 6 +++--- .../src/lib/ast/wrappers/pipeline-wrapper.ts | 12 ++++++------ .../src/lib/validation/checks/block-definition.ts | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/libs/execution/src/lib/blocks/block-execution-util.ts b/libs/execution/src/lib/blocks/block-execution-util.ts index dc98b4c1b..c8e430085 100644 --- a/libs/execution/src/lib/blocks/block-execution-util.ts +++ b/libs/execution/src/lib/blocks/block-execution-util.ts @@ -54,7 +54,7 @@ export async function executeBlocks( for (const blockData of executionOrder) { const block = blockData.block; const parentData = pipelineWrapper - .getPredecessorBlocks(block) + .followIngoingPipes(block) .map((parent) => executionOrder.find((blockData) => parent === blockData.block), ); diff --git a/libs/interpreter-lib/src/interpreter.ts b/libs/interpreter-lib/src/interpreter.ts index 1234415bf..22b1cf114 100644 --- a/libs/interpreter-lib/src/interpreter.ts +++ b/libs/interpreter-lib/src/interpreter.ts @@ -255,7 +255,7 @@ export function logPipelineOverview( block.name } (${blockTypeName})`; const childString = pipelineWrapper - .getSuccessorBlocks(block) + .followOutgoingPipes(block) .map((child) => toString(child, depth + 1)) .join('\n'); return blockString + '\n' + childString; diff --git a/libs/language-server/src/lib/ast/model-util.ts b/libs/language-server/src/lib/ast/model-util.ts index 1fe2617cb..43cbf873b 100644 --- a/libs/language-server/src/lib/ast/model-util.ts +++ b/libs/language-server/src/lib/ast/model-util.ts @@ -53,10 +53,10 @@ export function getBlocksInTopologicalSorting( sortedNodes.push(node); - for (const childNode of pipelineWrapper.getSuccessorBlocks(node)) { + for (const childNode of pipelineWrapper.followOutgoingPipes(node)) { // Mark edges between parent and child as visited pipelineWrapper - .getPredecessorPipes(childNode) + .getIngoingPipes(childNode) .filter((e) => e.from === node) .forEach((e) => { unvisitedEdges = unvisitedEdges.filter((edge) => !edge.equals(e)); @@ -64,7 +64,7 @@ export function getBlocksInTopologicalSorting( // If all edges to the child have been visited const notRemovedEdges = pipelineWrapper - .getPredecessorPipes(childNode) + .getIngoingPipes(childNode) .filter((e) => unvisitedEdges.some((edge) => edge.equals(e))); if (notRemovedEdges.length === 0) { // Insert it into currentBlocks diff --git a/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts b/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts index 688355bec..fc12e660a 100644 --- a/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts +++ b/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts @@ -52,23 +52,23 @@ export class PipelineWrapper return this.getStartingBlockPipes().map((p) => p.from); } - getSuccessorPipes(blockDefinition: BlockDefinition): PipeWrapper[] { + getOutgoingPipes(blockDefinition: BlockDefinition): PipeWrapper[] { return this.allPipes.filter((pipe) => { return pipe.from === blockDefinition; }); } - getSuccessorBlocks(blockDefinition: BlockDefinition): BlockDefinition[] { - return this.getSuccessorPipes(blockDefinition).map((p) => p.to); + followOutgoingPipes(blockDefinition: BlockDefinition): BlockDefinition[] { + return this.getOutgoingPipes(blockDefinition).map((p) => p.to); } - getPredecessorPipes(blockDefinition: BlockDefinition): PipeWrapper[] { + getIngoingPipes(blockDefinition: BlockDefinition): PipeWrapper[] { return this.allPipes.filter((pipe) => { return pipe.to === blockDefinition; }); } - getPredecessorBlocks(blockDefinition: BlockDefinition): BlockDefinition[] { - return this.getPredecessorPipes(blockDefinition).map((p) => p.from); + followIngoingPipes(blockDefinition: BlockDefinition): BlockDefinition[] { + return this.getIngoingPipes(blockDefinition).map((p) => p.from); } } diff --git a/libs/language-server/src/lib/validation/checks/block-definition.ts b/libs/language-server/src/lib/validation/checks/block-definition.ts index a279af758..cd2aa118c 100644 --- a/libs/language-server/src/lib/validation/checks/block-definition.ts +++ b/libs/language-server/src/lib/validation/checks/block-definition.ts @@ -101,11 +101,11 @@ function collectPipes( let pipes: PipeWrapper[]; switch (whatToCheck) { case 'input': { - pipes = pipelineWrapper.getPredecessorPipes(block); + pipes = pipelineWrapper.getIngoingPipes(block); break; } case 'output': { - pipes = pipelineWrapper.getSuccessorPipes(block); + pipes = pipelineWrapper.getOutgoingPipes(block); break; } default: { From dc33dfed3e647066c9147e361248737ac5c08d81 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Tue, 19 Dec 2023 16:54:33 +0100 Subject: [PATCH 04/14] Add license to PipelineWrapper --- libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts b/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts index fc12e660a..f30c68750 100644 --- a/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts +++ b/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + import { BlockDefinition, CompositeBlocktypeDefinition, From 319488bba3d699e265d6adfe6c1b964acfabd8d6 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Tue, 19 Dec 2023 17:12:13 +0100 Subject: [PATCH 05/14] Remove duplicated starting blocks --- .../src/lib/ast/wrappers/pipeline-wrapper.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts b/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts index f30c68750..6f0b5022d 100644 --- a/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts +++ b/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts @@ -53,7 +53,12 @@ export class PipelineWrapper } getStartingBlocks(): BlockDefinition[] { - return this.getStartingBlockPipes().map((p) => p.from); + const startingBlocks = this.getStartingBlockPipes().map((p) => p.from); + + // Special case: the extractor is reused for multiple paths + // Thus, we remove duplicates + const withoutDuplicates = [...new Set(startingBlocks)]; + return withoutDuplicates; } getOutgoingPipes(blockDefinition: BlockDefinition): PipeWrapper[] { From 37f9c1c658ea351f138180e347d377de903400d3 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Tue, 19 Dec 2023 17:12:29 +0100 Subject: [PATCH 06/14] Alter GTFS static example to use composite block --- example/gtfs-static.jv | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/example/gtfs-static.jv b/example/gtfs-static.jv index e409abec1..7703ca1fe 100644 --- a/example/gtfs-static.jv +++ b/example/gtfs-static.jv @@ -14,79 +14,78 @@ pipeline GtfsPipeline { // 2. The origin for multiple pipe sequences is a zip // file. Each csv file in this zip is further processed // by its own sequence of blocks and pipes. - GTFSSampleFeedExtractor -> ZipArchiveInterpreter; - ZipArchiveInterpreter + GTFSSampleFeedExtractor -> AgencyFilePicker -> AgencyTextFileInterpreter -> AgencyCSVInterpreter -> AgencyTableInterpreter -> AgencyLoader; - ZipArchiveInterpreter + GTFSSampleFeedExtractor -> CalendarDatesFilePicker -> CalendarDatesTextFileInterpreter -> CalendarDatesCSVInterpreter -> CalendarDatesTableInterpreter -> CalendarDatesLoader; - ZipArchiveInterpreter + GTFSSampleFeedExtractor -> CalendarFilePicker -> CalendarTextFileInterpreter -> CalendarCSVInterpreter -> CalendarTableInterpreter -> CalendarLoader; - ZipArchiveInterpreter + GTFSSampleFeedExtractor -> FareAttributesFilePicker -> FareAttributesTextFileInterpreter -> FareAttributesCSVInterpreter -> FareAttributesTableInterpreter -> FareAttributesLoader; - ZipArchiveInterpreter + GTFSSampleFeedExtractor -> FareRulesFilePicker -> FareRulesTextFileInterpreter -> FareRulesCSVInterpreter -> FareRulesTableInterpreter -> FareRulesLoader; - ZipArchiveInterpreter + GTFSSampleFeedExtractor -> FrequenciesFilePicker -> FrequenciesTextFileInterpreter -> FrequenciesCSVInterpreter -> FrequenciesTableInterpreter -> FrequenciesLoader; - ZipArchiveInterpreter + GTFSSampleFeedExtractor -> RoutesFilePicker -> RoutesTextFileInterpreter -> RoutesCSVInterpreter -> RoutesTableInterpreter -> RoutesLoader; - ZipArchiveInterpreter + GTFSSampleFeedExtractor -> ShapesFilePicker -> ShapesTextFileInterpreter -> ShapesCSVInterpreter -> ShapesTableInterpreter -> ShapesLoader; - ZipArchiveInterpreter + GTFSSampleFeedExtractor -> StopTimesFilePicker -> StopTimesTextFileInterpreter -> StopTimesCSVInterpreter -> StopTimesTableInterpreter -> StopTimesLoader; - ZipArchiveInterpreter + GTFSSampleFeedExtractor -> StopsFilePicker -> StopsTextFileInterpreter -> StopsCSVInterpreter -> StopsTableInterpreter -> StopsLoader; - ZipArchiveInterpreter + GTFSSampleFeedExtractor -> TripsFilePicker -> TripsTextFileInterpreter -> TripsCSVInterpreter @@ -94,14 +93,10 @@ pipeline GtfsPipeline { -> TripsLoader; // 3. As a first step, we download the zip file and interpret it. - block GTFSSampleFeedExtractor oftype HttpExtractor { + block GTFSSampleFeedExtractor oftype GTFSExtractor { url: "https://developers.google.com/static/transit/gtfs/examples/sample-feed.zip"; } - block ZipArchiveInterpreter oftype ArchiveInterpreter { - archiveType: "zip"; - } - // 4. Next, we pick several csv files (with the file extension ".txt") // for further processing . block AgencyFilePicker oftype FilePicker { From 46ae21d6c4bfc3e1d272624f6181d23ca4f1a448 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Wed, 20 Dec 2023 10:50:41 +0100 Subject: [PATCH 07/14] Rename pipesContainer argument of executeBlocks --- .../src/lib/blocks/block-execution-util.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/libs/execution/src/lib/blocks/block-execution-util.ts b/libs/execution/src/lib/blocks/block-execution-util.ts index c8e430085..45f0229e5 100644 --- a/libs/execution/src/lib/blocks/block-execution-util.ts +++ b/libs/execution/src/lib/blocks/block-execution-util.ts @@ -34,20 +34,16 @@ export interface ExecutionOrderItem { */ export async function executeBlocks( executionContext: ExecutionContext, - compositeBlockTypeDefinition: - | CompositeBlocktypeDefinition - | PipelineDefinition, + pipesContainer: CompositeBlocktypeDefinition | PipelineDefinition, initialInputValue: IOTypeImplementation | undefined = undefined, ): Promise> { - const pipelineWrapper = new PipelineWrapper(compositeBlockTypeDefinition); + const pipelineWrapper = new PipelineWrapper(pipesContainer); const executionOrder: { block: BlockDefinition; value: IOTypeImplementation | null; - }[] = getBlocksInTopologicalSorting(compositeBlockTypeDefinition).map( - (block) => { - return { block: block, value: NONE }; - }, - ); + }[] = getBlocksInTopologicalSorting(pipesContainer).map((block) => { + return { block: block, value: NONE }; + }); let isFirstBlock = true; From f1f3bee55b3cf5badf56f2bfd040f6b41ae4d8c4 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Wed, 20 Dec 2023 10:53:10 +0100 Subject: [PATCH 08/14] Rename arguments to pipesContainer in PipelineWrapper --- .../src/lib/ast/wrappers/pipeline-wrapper.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts b/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts index 6f0b5022d..7011ea099 100644 --- a/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts +++ b/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts @@ -19,17 +19,19 @@ export class PipelineWrapper allPipes: PipeWrapper[] = []; constructor( - pipelineDefinition: PipelineDefinition | CompositeBlocktypeDefinition, + pipesContainer: PipelineDefinition | CompositeBlocktypeDefinition, ) { - this.astNode = pipelineDefinition; + this.astNode = pipesContainer; - this.allPipes = pipelineDefinition.pipes.flatMap((pipe) => + this.allPipes = pipesContainer.pipes.flatMap((pipe) => createWrappersFromPipeChain(pipe), ); } - static canBeWrapped(pipelineDefinition: PipelineDefinition): boolean { - for (const pipeDefinition of pipelineDefinition.pipes) { + static canBeWrapped( + pipesContainer: PipelineDefinition | CompositeBlocktypeDefinition, + ): boolean { + for (const pipeDefinition of pipesContainer.pipes) { for ( let chainIndex = 0; chainIndex < pipeDefinition.blocks.length - 1; From 91e8987a22073a0b0ed90b0c406e7fb8cfb71a3b Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Wed, 20 Dec 2023 10:58:35 +0100 Subject: [PATCH 09/14] Rename followPipe methods to getChildBlock and getParentBlock --- libs/execution/src/lib/blocks/block-execution-util.ts | 2 +- libs/language-server/src/lib/ast/model-util.ts | 2 +- .../src/lib/ast/wrappers/pipeline-wrapper.ts | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/execution/src/lib/blocks/block-execution-util.ts b/libs/execution/src/lib/blocks/block-execution-util.ts index 45f0229e5..4d3e6239b 100644 --- a/libs/execution/src/lib/blocks/block-execution-util.ts +++ b/libs/execution/src/lib/blocks/block-execution-util.ts @@ -50,7 +50,7 @@ export async function executeBlocks( for (const blockData of executionOrder) { const block = blockData.block; const parentData = pipelineWrapper - .followIngoingPipes(block) + .getParentBlocks(block) .map((parent) => executionOrder.find((blockData) => parent === blockData.block), ); diff --git a/libs/language-server/src/lib/ast/model-util.ts b/libs/language-server/src/lib/ast/model-util.ts index 43cbf873b..91b9a01d2 100644 --- a/libs/language-server/src/lib/ast/model-util.ts +++ b/libs/language-server/src/lib/ast/model-util.ts @@ -53,7 +53,7 @@ export function getBlocksInTopologicalSorting( sortedNodes.push(node); - for (const childNode of pipelineWrapper.followOutgoingPipes(node)) { + for (const childNode of pipelineWrapper.getChildBlocks(node)) { // Mark edges between parent and child as visited pipelineWrapper .getIngoingPipes(childNode) diff --git a/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts b/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts index 7011ea099..34a8c79b7 100644 --- a/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts +++ b/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts @@ -47,9 +47,9 @@ export class PipelineWrapper getStartingBlockPipes(): PipeWrapper[] { return this.allPipes.filter((pipe) => { - const fromBlock = pipe.from; + const parentBlock = pipe.from; const isToOfOtherPipe = - this.allPipes.filter((p) => p.to === fromBlock).length > 0; + this.allPipes.filter((p) => p.to === parentBlock).length > 0; return !isToOfOtherPipe; }); } @@ -69,7 +69,7 @@ export class PipelineWrapper }); } - followOutgoingPipes(blockDefinition: BlockDefinition): BlockDefinition[] { + getChildBlocks(blockDefinition: BlockDefinition): BlockDefinition[] { return this.getOutgoingPipes(blockDefinition).map((p) => p.to); } @@ -79,7 +79,7 @@ export class PipelineWrapper }); } - followIngoingPipes(blockDefinition: BlockDefinition): BlockDefinition[] { + getParentBlocks(blockDefinition: BlockDefinition): BlockDefinition[] { return this.getIngoingPipes(blockDefinition).map((p) => p.from); } } From 0b71a9d0c6fa52b64bc5ec4d5d721ad272a46558 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Wed, 20 Dec 2023 11:02:35 +0100 Subject: [PATCH 10/14] Move getBlocksInTopologicalSorting function to PipelineWrapper --- .../src/lib/blocks/block-execution-util.ts | 3 +- .../language-server/src/lib/ast/model-util.ts | 69 +------------------ .../src/lib/ast/wrappers/pipeline-wrapper.ts | 56 +++++++++++++++ 3 files changed, 58 insertions(+), 70 deletions(-) diff --git a/libs/execution/src/lib/blocks/block-execution-util.ts b/libs/execution/src/lib/blocks/block-execution-util.ts index 4d3e6239b..57e27972e 100644 --- a/libs/execution/src/lib/blocks/block-execution-util.ts +++ b/libs/execution/src/lib/blocks/block-execution-util.ts @@ -7,7 +7,6 @@ import { CompositeBlocktypeDefinition, PipelineDefinition, PipelineWrapper, - getBlocksInTopologicalSorting, } from '@jvalue/jayvee-language-server'; import { ExecutionContext } from '../execution-context'; @@ -41,7 +40,7 @@ export async function executeBlocks( const executionOrder: { block: BlockDefinition; value: IOTypeImplementation | null; - }[] = getBlocksInTopologicalSorting(pipesContainer).map((block) => { + }[] = pipelineWrapper.getBlocksInTopologicalSorting().map((block) => { return { block: block, value: NONE }; }); diff --git a/libs/language-server/src/lib/ast/model-util.ts b/libs/language-server/src/lib/ast/model-util.ts index 91b9a01d2..b5c804a2a 100644 --- a/libs/language-server/src/lib/ast/model-util.ts +++ b/libs/language-server/src/lib/ast/model-util.ts @@ -2,85 +2,18 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { strict as assert } from 'assert'; - import { AstNode, LangiumDocuments } from 'langium'; import { BinaryExpression, - BlockDefinition, BuiltinBlocktypeDefinition, BuiltinConstrainttypeDefinition, - CompositeBlocktypeDefinition, - PipelineDefinition, UnaryExpression, isBuiltinBlocktypeDefinition, isJayveeModel, } from './generated/ast'; // eslint-disable-next-line import/no-cycle -import { - BlockTypeWrapper, - ConstraintTypeWrapper, - PipelineWrapper, -} from './wrappers'; - -/** - * Returns blocks in a pipeline in topological order, based on - * Kahn's algorithm. - * - * Considers a pipeline as a directed, acyclical graph where - * blocks are nodes and pipes are edges. A list in topological - * order has the property that parent nodes are always listed - * before their children. - * - * "[...] a list in topological order is such that no element - * appears in it until after all elements appearing on all paths - * leading to the particular element have been listed." - * - * Kahn, A. B. (1962). Topological sorting of large networks. Communications of the ACM, 5(11), 558–562. - */ -export function getBlocksInTopologicalSorting( - pipeline: PipelineDefinition | CompositeBlocktypeDefinition, -): BlockDefinition[] { - const pipelineWrapper = new PipelineWrapper(pipeline); - const sortedNodes = []; - const currentNodes = [...pipelineWrapper.getStartingBlocks()]; - let unvisitedEdges = [...pipelineWrapper.allPipes]; - - while (currentNodes.length > 0) { - const node = currentNodes.pop(); - assert(node !== undefined); - - sortedNodes.push(node); - - for (const childNode of pipelineWrapper.getChildBlocks(node)) { - // Mark edges between parent and child as visited - pipelineWrapper - .getIngoingPipes(childNode) - .filter((e) => e.from === node) - .forEach((e) => { - unvisitedEdges = unvisitedEdges.filter((edge) => !edge.equals(e)); - }); - - // If all edges to the child have been visited - const notRemovedEdges = pipelineWrapper - .getIngoingPipes(childNode) - .filter((e) => unvisitedEdges.some((edge) => edge.equals(e))); - if (notRemovedEdges.length === 0) { - // Insert it into currentBlocks - currentNodes.push(childNode); - } - } - } - - // If the graph still contains unvisited edges it is not a DAG - assert( - unvisitedEdges.length === 0, - `The pipeline ${pipeline.name} is expected to have no cycles`, - ); - - return sortedNodes; -} +import { BlockTypeWrapper, ConstraintTypeWrapper } from './wrappers'; export type UnaryExpressionOperator = UnaryExpression['operator']; export type BinaryExpressionOperator = BinaryExpression['operator']; diff --git a/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts b/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts index 34a8c79b7..acbc577a3 100644 --- a/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts +++ b/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: AGPL-3.0-only +import { strict as assert } from 'assert'; + import { BlockDefinition, CompositeBlocktypeDefinition, @@ -82,4 +84,58 @@ export class PipelineWrapper getParentBlocks(blockDefinition: BlockDefinition): BlockDefinition[] { return this.getIngoingPipes(blockDefinition).map((p) => p.from); } + + /** + * Returns blocks in a pipeline in topological order, based on + * Kahn's algorithm. + * + * Considers a pipeline as a directed, acyclical graph where + * blocks are nodes and pipes are edges. A list in topological + * order has the property that parent nodes are always listed + * before their children. + * + * "[...] a list in topological order is such that no element + * appears in it until after all elements appearing on all paths + * leading to the particular element have been listed." + * + * Kahn, A. B. (1962). Topological sorting of large networks. Communications of the ACM, 5(11), 558–562. + */ + getBlocksInTopologicalSorting(): BlockDefinition[] { + const sortedNodes = []; + const currentNodes = [...this.getStartingBlocks()]; + let unvisitedEdges = [...this.allPipes]; + + while (currentNodes.length > 0) { + const node = currentNodes.pop(); + assert(node !== undefined); + + sortedNodes.push(node); + + for (const childNode of this.getChildBlocks(node)) { + // Mark edges between parent and child as visited + this.getIngoingPipes(childNode) + .filter((e) => e.from === node) + .forEach((e) => { + unvisitedEdges = unvisitedEdges.filter((edge) => !edge.equals(e)); + }); + + // If all edges to the child have been visited + const notRemovedEdges = this.getIngoingPipes(childNode).filter((e) => + unvisitedEdges.some((edge) => edge.equals(e)), + ); + if (notRemovedEdges.length === 0) { + // Insert it into currentBlocks + currentNodes.push(childNode); + } + } + } + + // If the graph still contains unvisited edges it is not a DAG + assert( + unvisitedEdges.length === 0, + `The pipeline ${this.astNode.name} is expected to have no cycles`, + ); + + return sortedNodes; + } } From 32c87d9b47dcb6b97b3accc2b89da2354e65b565 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Wed, 20 Dec 2023 11:04:34 +0100 Subject: [PATCH 11/14] Update use of old followPipe method in interpreter-lib --- libs/interpreter-lib/src/interpreter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/interpreter-lib/src/interpreter.ts b/libs/interpreter-lib/src/interpreter.ts index 22b1cf114..f0d114673 100644 --- a/libs/interpreter-lib/src/interpreter.ts +++ b/libs/interpreter-lib/src/interpreter.ts @@ -255,7 +255,7 @@ export function logPipelineOverview( block.name } (${blockTypeName})`; const childString = pipelineWrapper - .followOutgoingPipes(block) + .getChildBlocks(block) .map((child) => toString(child, depth + 1)) .join('\n'); return blockString + '\n' + childString; From cda9e9aad22187df635b3d15c843369f27eddd39 Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Fri, 12 Jan 2024 17:58:24 +0100 Subject: [PATCH 12/14] Ensure pipeline wrapper can be created in validations --- .../src/lib/validation/checks/block-definition.ts | 6 +++++- .../src/lib/validation/checks/pipeline-definition.ts | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/libs/language-server/src/lib/validation/checks/block-definition.ts b/libs/language-server/src/lib/validation/checks/block-definition.ts index cd2aa118c..87da1ae64 100644 --- a/libs/language-server/src/lib/validation/checks/block-definition.ts +++ b/libs/language-server/src/lib/validation/checks/block-definition.ts @@ -31,7 +31,10 @@ function checkPipesOfBlock( whatToCheck: 'input' | 'output', context: ValidationContext, ): void { - if (!BlockTypeWrapper.canBeWrapped(block?.type)) { + if ( + !BlockTypeWrapper.canBeWrapped(block?.type) || + !PipelineWrapper.canBeWrapped(block.$container) + ) { return; } const blockType = new BlockTypeWrapper(block?.type); @@ -98,6 +101,7 @@ function collectPipes( whatToCheck: 'input' | 'output', ): PipeWrapper[] { const pipelineWrapper = new PipelineWrapper(block.$container); + let pipes: PipeWrapper[]; switch (whatToCheck) { case 'input': { diff --git a/libs/language-server/src/lib/validation/checks/pipeline-definition.ts b/libs/language-server/src/lib/validation/checks/pipeline-definition.ts index 35a2198ad..1ccffc8c7 100644 --- a/libs/language-server/src/lib/validation/checks/pipeline-definition.ts +++ b/libs/language-server/src/lib/validation/checks/pipeline-definition.ts @@ -22,7 +22,11 @@ function checkStartingBlocks( pipeline: PipelineDefinition, context: ValidationContext, ): void { + if (PipelineWrapper.canBeWrapped(pipeline)) { + return; + } const pipelineWrapper = new PipelineWrapper(pipeline); + const startingBlocks = pipelineWrapper.getStartingBlocks(); if (startingBlocks.length === 0) { context.accept( From a224ee9c6f5129951349611791fe99f2739092ca Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Mon, 15 Jan 2024 14:58:32 +0100 Subject: [PATCH 13/14] Fix bug in pipeline validation --- .../src/lib/validation/checks/pipeline-definition.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/language-server/src/lib/validation/checks/pipeline-definition.ts b/libs/language-server/src/lib/validation/checks/pipeline-definition.ts index 1ccffc8c7..ca7992a7b 100644 --- a/libs/language-server/src/lib/validation/checks/pipeline-definition.ts +++ b/libs/language-server/src/lib/validation/checks/pipeline-definition.ts @@ -22,7 +22,7 @@ function checkStartingBlocks( pipeline: PipelineDefinition, context: ValidationContext, ): void { - if (PipelineWrapper.canBeWrapped(pipeline)) { + if (!PipelineWrapper.canBeWrapped(pipeline)) { return; } const pipelineWrapper = new PipelineWrapper(pipeline); From 815a580f4f4112fae01c7ebe64903fa2b58223cf Mon Sep 17 00:00:00 2001 From: Georg Schwarz Date: Mon, 15 Jan 2024 15:02:38 +0100 Subject: [PATCH 14/14] Improve typing of PipelineWrapper --- .../src/lib/ast/wrappers/pipeline-wrapper.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts b/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts index acbc577a3..f5d40c123 100644 --- a/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts +++ b/libs/language-server/src/lib/ast/wrappers/pipeline-wrapper.ts @@ -13,16 +13,15 @@ import { import { AstNodeWrapper } from './ast-node-wrapper'; import { PipeWrapper, createWrappersFromPipeChain } from './pipe-wrapper'; -export class PipelineWrapper - implements AstNodeWrapper +export class PipelineWrapper< + T extends PipelineDefinition | CompositeBlocktypeDefinition, +> implements AstNodeWrapper { - public readonly astNode: PipelineDefinition | CompositeBlocktypeDefinition; + public readonly astNode: T; allPipes: PipeWrapper[] = []; - constructor( - pipesContainer: PipelineDefinition | CompositeBlocktypeDefinition, - ) { + constructor(pipesContainer: T) { this.astNode = pipesContainer; this.allPipes = pipesContainer.pipes.flatMap((pipe) =>