diff --git a/apps/docs/docs/user/core-concepts.md b/apps/docs/docs/user/core-concepts.md index 4c8cf91a0..1ad04355a 100644 --- a/apps/docs/docs/user/core-concepts.md +++ b/apps/docs/docs/user/core-concepts.md @@ -29,6 +29,7 @@ pipeline CarsPipeline { A _block_ is a processing step within a _pipeline_. It can have a default input and a default output. We differentiate the following types of _blocks_: + - _Extractor blocks_ do not have a default input but only a default output. They model a **data source**. - _Transformator blocks_ have a default input and a default output. They model a **transformation**. - _Loader blocks_ do have a default input but nor a default output. They model a **data sink**. @@ -49,7 +50,7 @@ The availability of property keys and their respective _value types_ is determin block GasReserveHttpExtractor oftype HttpExtractor { // key: value url: "https://www.bundesnetzagentur.de/_tools/SVG/js2/_functions/csv_export.html?view=renderCSV&id=1089590"; -} +} ``` In the example above, the `url` property of type `text` is defined by the corresponding `HttpExtractor` _block type_. @@ -61,10 +62,11 @@ _Blocks_ can be either defined as part of the language, called _built-in_ or def A _value type_ is the definition of a data type of the processed data. Some _blocks_ use _value types_ to define logic (like filtering or assessing the data type in a data sink). We differentiate the following kinds of _value types_: + - _Built-in value types_ come with the basic version of Jayvee. See [built-in value types](./value-types/built-in-value-types). - _Primitive value types_ can be defined by the user to model domain-specific data types and represent a single value. _Constraints_ can be added to a _primitive value types_. -See [primitive value types](./value-types/primitive-value-types). + See [primitive value types](./value-types/primitive-value-types). - _Compound value types_: UPCOMING. ```jayvee @@ -77,6 +79,7 @@ constraint GasFillLevelRange on decimal: ``` ## Transforms + _Transforms_ are used to transform data from one _value type_ to a different one. For more details, see [transforms](./transforms.md). ```jayvee @@ -86,4 +89,36 @@ transform CelsiusToKelvin { tempKelvin: tempCelsius + 273.15; } -``` \ No newline at end of file +``` + +## Publishing / using model elements + +If you want to use a model element in a different file other than the one you define it, you need to _publish_ and _use_ it. + +1. Publish the element to make it usable in other files. + +```jayvee +// Either publish right away when defining an element +publish constraint GasFillLevelRange on decimal: + value >= 0 and value <= 100; + +// Or define first and publish separately +constraint GasFillLevelRange on decimal: + value >= 0 and value <= 100; + +publish GasFillLevelRange; +``` + +2. Use the element in another file + +```jayvee +// Define from where you want to take elements +use * from './relative/path/to/file.jv'; + +// Then just use them as if they were defined on root level +valuetype GasFillLevel oftype integer { + constraints: [ GasFillLevelRange ]; // GasFillLevelRange is defined in another file +} +``` + +Currently, only root-level elements can be published, so elements defined within a pipeline cannot be used in other files. diff --git a/libs/execution/src/lib/constraints/executors/allowlist-constraint-executor.spec.ts b/libs/execution/src/lib/constraints/executors/allowlist-constraint-executor.spec.ts index d623ff64c..90fd67a4b 100644 --- a/libs/execution/src/lib/constraints/executors/allowlist-constraint-executor.spec.ts +++ b/libs/execution/src/lib/constraints/executors/allowlist-constraint-executor.spec.ts @@ -8,7 +8,9 @@ import { type JayveeServices, type TypedConstraintDefinition, createJayveeServices, + isTypedConstraintDefinition, } from '@jvalue/jayvee-language-server'; +import { extractTestElements } from '@jvalue/jayvee-language-server/test'; import { type ParseHelperOptions, expectNoParserAndLexerErrors, @@ -51,10 +53,12 @@ describe('Validation of AllowlistConstraintExecutor', () => { document.parseResult.value, 'pipelines@0/blocks@2', ) as BlockDefinition; - const constraint = locator.getAstNode( - document.parseResult.value, - 'constraints@0', - ) as TypedConstraintDefinition; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const constraint = extractTestElements( + document, + (x): x is TypedConstraintDefinition => isTypedConstraintDefinition(x), + )[0]!; return new AllowlistConstraintExecutor().isValid( value, diff --git a/libs/execution/src/lib/constraints/executors/denylist-constraint-executor.spec.ts b/libs/execution/src/lib/constraints/executors/denylist-constraint-executor.spec.ts index 8a0fefb03..686bb213f 100644 --- a/libs/execution/src/lib/constraints/executors/denylist-constraint-executor.spec.ts +++ b/libs/execution/src/lib/constraints/executors/denylist-constraint-executor.spec.ts @@ -8,10 +8,12 @@ import { type JayveeServices, type TypedConstraintDefinition, createJayveeServices, + isTypedConstraintDefinition, } from '@jvalue/jayvee-language-server'; import { type ParseHelperOptions, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, } from '@jvalue/jayvee-language-server/test'; @@ -51,10 +53,12 @@ describe('Validation of DenylistConstraintExecutor', () => { document.parseResult.value, 'pipelines@0/blocks@2', ) as BlockDefinition; - const constraint = locator.getAstNode( - document.parseResult.value, - 'constraints@0', - ) as TypedConstraintDefinition; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const constraint = extractTestElements( + document, + (x): x is TypedConstraintDefinition => isTypedConstraintDefinition(x), + )[0]!; return new DenylistConstraintExecutor().isValid( value, diff --git a/libs/execution/src/lib/constraints/executors/expression-constraint-executor.spec.ts b/libs/execution/src/lib/constraints/executors/expression-constraint-executor.spec.ts index eb64d0790..c23c20f12 100644 --- a/libs/execution/src/lib/constraints/executors/expression-constraint-executor.spec.ts +++ b/libs/execution/src/lib/constraints/executors/expression-constraint-executor.spec.ts @@ -8,10 +8,12 @@ import { type InternalValueRepresentation, type JayveeServices, createJayveeServices, + isExpressionConstraintDefinition, } from '@jvalue/jayvee-language-server'; import { type ParseHelperOptions, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, } from '@jvalue/jayvee-language-server/test'; @@ -51,10 +53,13 @@ describe('Validation of AllowlistConstraintExecutor', () => { document.parseResult.value, 'pipelines@0/blocks@2', ) as BlockDefinition; - const constraint = locator.getAstNode( - document.parseResult.value, - 'constraints@0', - ) as ExpressionConstraintDefinition; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const constraint = extractTestElements( + document, + (x): x is ExpressionConstraintDefinition => + isExpressionConstraintDefinition(x), + )[0]!; return new ExpressionConstraintExecutor(constraint).isValid( value, diff --git a/libs/execution/src/lib/constraints/executors/length-constraint-executor.spec.ts b/libs/execution/src/lib/constraints/executors/length-constraint-executor.spec.ts index 63c7a6769..2e70b3868 100644 --- a/libs/execution/src/lib/constraints/executors/length-constraint-executor.spec.ts +++ b/libs/execution/src/lib/constraints/executors/length-constraint-executor.spec.ts @@ -8,10 +8,12 @@ import { type JayveeServices, type TypedConstraintDefinition, createJayveeServices, + isTypedConstraintDefinition, } from '@jvalue/jayvee-language-server'; import { type ParseHelperOptions, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, } from '@jvalue/jayvee-language-server/test'; @@ -51,10 +53,12 @@ describe('Validation of LengthConstraintExecutor', () => { document.parseResult.value, 'pipelines@0/blocks@2', ) as BlockDefinition; - const constraint = locator.getAstNode( - document.parseResult.value, - 'constraints@0', - ) as TypedConstraintDefinition; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const constraint = extractTestElements( + document, + (x): x is TypedConstraintDefinition => isTypedConstraintDefinition(x), + )[0]!; return new LengthConstraintExecutor().isValid( value, diff --git a/libs/execution/src/lib/constraints/executors/range-constraint-executor.spec.ts b/libs/execution/src/lib/constraints/executors/range-constraint-executor.spec.ts index 57361710e..e05450143 100644 --- a/libs/execution/src/lib/constraints/executors/range-constraint-executor.spec.ts +++ b/libs/execution/src/lib/constraints/executors/range-constraint-executor.spec.ts @@ -9,10 +9,12 @@ import { type TypedConstraintDefinition, createJayveeServices, initializeWorkspace, + isTypedConstraintDefinition, } from '@jvalue/jayvee-language-server'; import { type ParseHelperOptions, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, } from '@jvalue/jayvee-language-server/test'; @@ -52,10 +54,12 @@ describe('Validation of RangeConstraintExecutor', () => { document.parseResult.value, 'pipelines@0/blocks@2', ) as BlockDefinition; - const constraint = locator.getAstNode( - document.parseResult.value, - 'constraints@0', - ) as TypedConstraintDefinition; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const constraint = extractTestElements( + document, + (x): x is TypedConstraintDefinition => isTypedConstraintDefinition(x), + )[0]!; return new RangeConstraintExecutor().isValid( value, diff --git a/libs/execution/src/lib/constraints/executors/regex-constraint-executor.spec.ts b/libs/execution/src/lib/constraints/executors/regex-constraint-executor.spec.ts index e1b9c764f..49ee99c84 100644 --- a/libs/execution/src/lib/constraints/executors/regex-constraint-executor.spec.ts +++ b/libs/execution/src/lib/constraints/executors/regex-constraint-executor.spec.ts @@ -8,10 +8,12 @@ import { type JayveeServices, type TypedConstraintDefinition, createJayveeServices, + isTypedConstraintDefinition, } from '@jvalue/jayvee-language-server'; import { type ParseHelperOptions, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, } from '@jvalue/jayvee-language-server/test'; @@ -51,10 +53,12 @@ describe('Validation of RegexConstraintExecutor', () => { document.parseResult.value, 'pipelines@0/blocks@2', ) as BlockDefinition; - const constraint = locator.getAstNode( - document.parseResult.value, - 'constraints@0', - ) as TypedConstraintDefinition; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const constraint = extractTestElements( + document, + (x): x is TypedConstraintDefinition => isTypedConstraintDefinition(x), + )[0]!; return new RegexConstraintExecutor().isValid( value, diff --git a/libs/execution/src/lib/transforms/transform-executor.spec.ts b/libs/execution/src/lib/transforms/transform-executor.spec.ts index dd1bf462e..a0bcf1c5e 100644 --- a/libs/execution/src/lib/transforms/transform-executor.spec.ts +++ b/libs/execution/src/lib/transforms/transform-executor.spec.ts @@ -11,10 +11,12 @@ import { type JayveeServices, type TransformDefinition, createJayveeServices, + isTransformDefinition, } from '@jvalue/jayvee-language-server'; import { type ParseHelperOptions, expectNoParserAndLexerErrors, + extractTestElements, loadTestExtensions, parseHelper, readJvTestAssetHelper, @@ -77,10 +79,11 @@ describe('Validation of TransformExecutor', () => { const document = await parse(input, { validation: true }); expectNoParserAndLexerErrors(document); - const transform = locator.getAstNode( - document.parseResult.value, - 'transforms@0', - ) as TransformDefinition; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const transform = extractTestElements( + document, + (x): x is TransformDefinition => isTransformDefinition(x), + )[0]!; const executionContext = getTestExecutionContext( locator, diff --git a/libs/execution/test/assets/transform-executor/test-extension/TestBlockTypes.jv b/libs/execution/test/assets/transform-executor/test-extension/TestBlockTypes.jv index bf5bb4357..593135475 100644 --- a/libs/execution/test/assets/transform-executor/test-extension/TestBlockTypes.jv +++ b/libs/execution/test/assets/transform-executor/test-extension/TestBlockTypes.jv @@ -2,17 +2,17 @@ // // SPDX-License-Identifier: AGPL-3.0-only -builtin blocktype TestFileExtractor { +publish builtin blocktype TestFileExtractor { input inPort oftype None; output outPort oftype File; } -builtin blocktype TestFileLoader { +publish builtin blocktype TestFileLoader { input inPort oftype File; output outPort oftype None; } -builtin blocktype TestTableLoader { +publish builtin blocktype TestTableLoader { input inPort oftype Table; output outPort oftype None; } diff --git a/libs/extensions/rdbms/exec/test/test-extension/TestBlockTypes.jv b/libs/extensions/rdbms/exec/test/test-extension/TestBlockTypes.jv index e050777aa..89f6fef78 100644 --- a/libs/extensions/rdbms/exec/test/test-extension/TestBlockTypes.jv +++ b/libs/extensions/rdbms/exec/test/test-extension/TestBlockTypes.jv @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -builtin blocktype TestTableExtractor { +publish builtin blocktype TestTableExtractor { input inPort oftype None; output outPort oftype Table; } diff --git a/libs/extensions/std/exec/test/test-extension/TestBlockTypes.jv b/libs/extensions/std/exec/test/test-extension/TestBlockTypes.jv index 3abfdb4e2..39921f8fe 100644 --- a/libs/extensions/std/exec/test/test-extension/TestBlockTypes.jv +++ b/libs/extensions/std/exec/test/test-extension/TestBlockTypes.jv @@ -2,22 +2,22 @@ // // SPDX-License-Identifier: AGPL-3.0-only -builtin blocktype TestFileExtractor { +publish builtin blocktype TestFileExtractor { input inPort oftype None; output outPort oftype File; } -builtin blocktype TestFileLoader { +publish builtin blocktype TestFileLoader { input inPort oftype File; output outPort oftype None; } -builtin blocktype TestSheetLoader { +publish builtin blocktype TestSheetLoader { input inPort oftype Sheet; output outPort oftype None; } -builtin blocktype TestTextFileLoader { +publish builtin blocktype TestTextFileLoader { input inPort oftype TextFile; output outPort oftype None; } diff --git a/libs/extensions/tabular/exec/test/test-extension/TestBlockTypes.jv b/libs/extensions/tabular/exec/test/test-extension/TestBlockTypes.jv index ba08556c4..c8fcba9ee 100644 --- a/libs/extensions/tabular/exec/test/test-extension/TestBlockTypes.jv +++ b/libs/extensions/tabular/exec/test/test-extension/TestBlockTypes.jv @@ -2,42 +2,42 @@ // // SPDX-License-Identifier: AGPL-3.0-only -builtin blocktype TestSheetExtractor { +publish builtin blocktype TestSheetExtractor { input inPort oftype None; output outPort oftype Sheet; } -builtin blocktype TestSheetLoader { +publish builtin blocktype TestSheetLoader { input inPort oftype Sheet; output outPort oftype None; } -builtin blocktype TestTextFileExtractor { +publish builtin blocktype TestTextFileExtractor { input inPort oftype None; output outPort oftype TextFile; } -builtin blocktype TestFileExtractor { +publish builtin blocktype TestFileExtractor { input inPort oftype None; output outPort oftype File; } -builtin blocktype TestWorkbookExtractor { +publish builtin blocktype TestWorkbookExtractor { input inPort oftype None; output outPort oftype Workbook; } -builtin blocktype TestWorkbookLoader { +publish builtin blocktype TestWorkbookLoader { input inPort oftype Workbook; output outPort oftype None; } -builtin blocktype TestTableExtractor { +publish builtin blocktype TestTableExtractor { input inPort oftype None; output outPort oftype Table; } -builtin blocktype TestTableLoader { +publish builtin blocktype TestTableLoader { input inPort oftype Table; output outPort oftype None; } diff --git a/libs/interpreter-lib/test/assets/parsing-util/test-extension/TestBlockTypes.jv b/libs/interpreter-lib/test/assets/parsing-util/test-extension/TestBlockTypes.jv index bf5bb4357..593135475 100644 --- a/libs/interpreter-lib/test/assets/parsing-util/test-extension/TestBlockTypes.jv +++ b/libs/interpreter-lib/test/assets/parsing-util/test-extension/TestBlockTypes.jv @@ -2,17 +2,17 @@ // // SPDX-License-Identifier: AGPL-3.0-only -builtin blocktype TestFileExtractor { +publish builtin blocktype TestFileExtractor { input inPort oftype None; output outPort oftype File; } -builtin blocktype TestFileLoader { +publish builtin blocktype TestFileLoader { input inPort oftype File; output outPort oftype None; } -builtin blocktype TestTableLoader { +publish builtin blocktype TestTableLoader { input inPort oftype Table; output outPort oftype None; } diff --git a/libs/interpreter-lib/test/assets/runtime-parameter-literal/test-extension/TestBlockTypes.jv b/libs/interpreter-lib/test/assets/runtime-parameter-literal/test-extension/TestBlockTypes.jv index 75e51a92f..a70b379a6 100644 --- a/libs/interpreter-lib/test/assets/runtime-parameter-literal/test-extension/TestBlockTypes.jv +++ b/libs/interpreter-lib/test/assets/runtime-parameter-literal/test-extension/TestBlockTypes.jv @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -builtin blocktype TestProperty { +publish builtin blocktype TestProperty { input inPort oftype File; output outPort oftype Table; diff --git a/libs/language-server/src/grammar/main.langium b/libs/language-server/src/grammar/main.langium index eb58f07ff..a7d824aa2 100644 --- a/libs/language-server/src/grammar/main.langium +++ b/libs/language-server/src/grammar/main.langium @@ -16,15 +16,24 @@ import './io-type' entry JayveeModel: ( imports+=ImportDefinition + | exports+=ExportDefinition + | exportableElements+=ExportableElementDefinition | pipelines+=PipelineDefinition - | valueTypes+=(CustomValuetypeDefinition | BuiltinValuetypeDefinition) - | constraints+=ConstraintDefinition - | transforms+=TransformDefinition - | blockTypes+=ReferenceableBlockTypeDefinition - | constrainttypes+=BuiltinConstrainttypeDefinition - | iotypes+=IotypeDefinition )*; +ExportableElementDefinition: + (isPublished?='publish')? ExportableElement; + +ExportableElement: + (CustomValuetypeDefinition | BuiltinValuetypeDefinition) + | ConstraintDefinition + | TransformDefinition + | ReferenceableBlockTypeDefinition + | BuiltinConstrainttypeDefinition + | IotypeDefinition; + +ExportDefinition: + 'publish' element=[ExportableElement] ';'; ImportDefinition: 'use' '*' 'from' path=STRING ';'; diff --git a/libs/language-server/src/lib/ast/expressions/test-utils.ts b/libs/language-server/src/lib/ast/expressions/test-utils.ts index bc6eff62e..1bcc9ccb9 100644 --- a/libs/language-server/src/lib/ast/expressions/test-utils.ts +++ b/libs/language-server/src/lib/ast/expressions/test-utils.ts @@ -2,11 +2,13 @@ // // SPDX-License-Identifier: AGPL-3.0-only +import { AstUtils } from 'langium'; import { NodeFileSystem } from 'langium/node'; +import { expect } from 'vitest'; import { parseHelper } from '../../../test/langium-utils'; import { createJayveeServices } from '../../jayvee-module'; -import { type TransformDefinition } from '../generated/ast'; +import { isTransformDefinition } from '../generated/ast'; import { evaluateExpression } from './evaluate-expression'; import { EvaluationContext } from './evaluation-context'; @@ -34,7 +36,6 @@ export async function executeExpressionTestHelper( ): Promise { const services = createJayveeServices(NodeFileSystem).Jayvee; const parse = parseHelper(services); - const locator = services.workspace.AstNodeLocator; const document = await parse(` transform TestTransform { @@ -45,10 +46,11 @@ export async function executeExpressionTestHelper( } `); - const transform = locator.getAstNode( - document.parseResult.value, - 'transforms@0', - ) as TransformDefinition; + const allElements = AstUtils.streamAllContents(document.parseResult.value); + const allTransforms = [...allElements.filter(isTransformDefinition)]; + expect(allTransforms.length).toBe(1); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const transform = allTransforms[0]!; const evaluationContext = new EvaluationContext( services.RuntimeParameterProvider, diff --git a/libs/language-server/src/lib/ast/model-util.ts b/libs/language-server/src/lib/ast/model-util.ts index dae9011bc..f1a5b04db 100644 --- a/libs/language-server/src/lib/ast/model-util.ts +++ b/libs/language-server/src/lib/ast/model-util.ts @@ -2,12 +2,13 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { type AstNode, type LangiumDocuments } from 'langium'; +import { type AstNode, AstUtils, type LangiumDocuments } from 'langium'; import { type BuiltinBlockTypeDefinition, type BuiltinConstrainttypeDefinition, isBuiltinBlockTypeDefinition, + isBuiltinConstrainttypeDefinition, isJayveeModel, } from './generated/ast'; import { @@ -59,11 +60,10 @@ export function getAllBuiltinBlockTypes( if (!isJayveeModel(parsedDocument)) { throw new Error('Expected parsed document to be a JayveeModel'); } - parsedDocument.blockTypes.forEach((blockTypeDefinition) => { - if (!isBuiltinBlockTypeDefinition(blockTypeDefinition)) { - return; - } - + const allBlockTypes = AstUtils.streamAllContents(parsedDocument).filter( + isBuiltinBlockTypeDefinition, + ); + allBlockTypes.forEach((blockTypeDefinition) => { const wasAlreadyVisited = visitedBuiltinBlockTypeDefinitions.has(blockTypeDefinition); if (wasAlreadyVisited) { @@ -100,7 +100,10 @@ export function getAllBuiltinConstraintTypes( if (!isJayveeModel(parsedDocument)) { throw new Error('Expected parsed document to be a JayveeModel'); } - parsedDocument.constrainttypes.forEach((constraintTypeDefinition) => { + const allConstraintTypes = AstUtils.streamAllContents( + parsedDocument, + ).filter(isBuiltinConstrainttypeDefinition); + allConstraintTypes.forEach((constraintTypeDefinition) => { const wasAlreadyVisited = visitedBuiltinConstraintTypeDefinitions.has( constraintTypeDefinition, ); diff --git a/libs/language-server/src/lib/builtin-library/stdlib.ts b/libs/language-server/src/lib/builtin-library/stdlib.ts index ccbfd14c8..375259d01 100644 --- a/libs/language-server/src/lib/builtin-library/stdlib.ts +++ b/libs/language-server/src/lib/builtin-library/stdlib.ts @@ -12,7 +12,7 @@ export function getBuiltinValuetypesLib() { .map(parseBuiltinValuetypeToJayvee); const collectionValuetype = `${parseAsComment('For internal use only.')} -builtin valuetype Collection;`; +publish builtin valuetype Collection;`; return { 'builtin:///stdlib/builtin-value-types.jv': [ @@ -24,7 +24,7 @@ builtin valuetype Collection;`; export const IOtypesLib = { 'builtin:///stdlib/io-types.jv': Object.values(IOType) - .map((iotype) => `builtin iotype ${iotype};`) + .map((iotype) => `publish builtin iotype ${iotype};`) .join('\n\n'), }; @@ -46,7 +46,7 @@ function parseBuiltinValuetypeToJayvee(valueType: PrimitiveValueType): string { if (!valueType.isReferenceableByUser()) { lines.push(parseAsComment('For internal use only.')); } - lines.push(`builtin valuetype ${valueType.getName()};`); + lines.push(`publish builtin valuetype ${valueType.getName()};`); return lines.join('\n'); } diff --git a/libs/language-server/src/lib/jayvee-module.ts b/libs/language-server/src/lib/jayvee-module.ts index 2ca8d8f79..2079f6e6e 100644 --- a/libs/language-server/src/lib/jayvee-module.ts +++ b/libs/language-server/src/lib/jayvee-module.ts @@ -32,7 +32,6 @@ import { JayveeDefinitionProvider, JayveeFormatter, JayveeHoverProvider, - JayveeScopeComputation, JayveeScopeProvider, } from './lsp'; import { JayveeImportResolver } from './services/import-resolver'; @@ -91,7 +90,6 @@ export const JayveeModule: Module< }, references: { ScopeProvider: (services) => new JayveeScopeProvider(services), - ScopeComputation: (service) => new JayveeScopeComputation(service), }, RuntimeParameterProvider: () => new RuntimeParameterProvider(), operators: { diff --git a/libs/language-server/src/lib/lsp/import-reference.spec.ts b/libs/language-server/src/lib/lsp/import-reference.spec.ts index 226044271..ed7051280 100644 --- a/libs/language-server/src/lib/lsp/import-reference.spec.ts +++ b/libs/language-server/src/lib/lsp/import-reference.spec.ts @@ -41,51 +41,111 @@ describe('References to imported elements', () => { services = createJayveeServices(NodeFileSystem).Jayvee; }); - const validCases: [string, string][] = [ - // [test name, test file path] - [ - 'should resolve reference to imported element', - 'import-published-references/valid-reference-to-imported-element.jv', - ], - [ - 'should resolve reference to transitively imported element', - 'import-published-references/valid-reference-to-element-transitive.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(); + describe('when element is directly published in element definition', () => { + const validCases: [string, string][] = [ + // [test name, test file path] + [ + 'should resolve reference to imported element', + 'import-published-references/published-via-element-definition/valid-reference-to-imported-element.jv', + ], + [ + 'should resolve reference to transitively imported element', + 'import-published-references/published-via-element-definition/valid-reference-to-element-transitive.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 non-existing element', + 'import-published-references/published-via-element-definition/invalid-reference-to-not-existing-element.jv', + ], + [ + 'should not resolve reference to non-imported element', + 'import-published-references/published-via-element-definition/invalid-reference-to-not-imported-element.jv', + ], + [ + 'should not resolve reference to not re-published transitive element', + 'import-published-references/published-via-element-definition/invalid-reference-to-element-transitive-no-republish.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(); + }); }); - const invalidCases: [string, string][] = [ - // [test name, test file path] - [ - 'should not resolve reference to non-existing element', - 'import-published-references/invalid-reference-to-not-existing-element.jv', - ], - [ - 'should not resolve reference to non-imported element', - 'import-published-references/invalid-reference-to-not-imported-element.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(); + describe('when element is published delayed via export definition', () => { + const validCases: [string, string][] = [ + // [test name, test file path] + [ + 'should resolve reference to imported element', + 'import-published-references/published-via-publish-definition/valid-reference-to-imported-element.jv', + ], + [ + 'should resolve reference to transitively imported element', + 'import-published-references/published-via-publish-definition/valid-reference-to-element-transitive.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 non-existing element', + 'import-published-references/published-via-publish-definition/invalid-reference-to-not-existing-element.jv', + ], + [ + 'should not resolve reference to non-imported element', + 'import-published-references/published-via-publish-definition/invalid-reference-to-not-imported-element.jv', + ], + [ + 'should not resolve reference to not re-published transitive element', + 'import-published-references/published-via-publish-definition/invalid-reference-to-element-transitive-no-republish.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(); + }); }); }); diff --git a/libs/language-server/src/lib/lsp/index.ts b/libs/language-server/src/lib/lsp/index.ts index 1907e2f15..46df76128 100644 --- a/libs/language-server/src/lib/lsp/index.ts +++ b/libs/language-server/src/lib/lsp/index.ts @@ -5,7 +5,6 @@ export * from './jayvee-completion-provider'; export * from './jayvee-formatter'; export * from './jayvee-hover-provider'; -export * from './jayvee-scope-computation'; export * from './jayvee-scope-provider'; export * from './jayvee-definition-provider'; export * from './jayvee-code-action-provider'; diff --git a/libs/language-server/src/lib/lsp/jayvee-completion-provider.ts b/libs/language-server/src/lib/lsp/jayvee-completion-provider.ts index c7fdbb768..0aebde26f 100644 --- a/libs/language-server/src/lib/lsp/jayvee-completion-provider.ts +++ b/libs/language-server/src/lib/lsp/jayvee-completion-provider.ts @@ -7,6 +7,7 @@ import { strict as assert } from 'assert'; import { type AstNode, + AstUtils, type LangiumDocument, type LangiumDocuments, type MaybePromise, @@ -35,6 +36,7 @@ import { isJayveeModel, isPropertyAssignment, isPropertyBody, + isValuetypeDefinition, } from '../ast/generated/ast'; import { getAllBuiltinBlockTypes, @@ -159,7 +161,10 @@ export class JayveeCompletionProvider extends DefaultCompletionProvider { if (!isJayveeModel(parsedDocument)) { throw new Error('Expected parsed document to be a JayveeModel'); } - parsedDocument.valueTypes.forEach((valueTypeDefinition) => { + const allValueTypes = AstUtils.streamAllContents(parsedDocument).filter( + isValuetypeDefinition, + ); + allValueTypes.forEach((valueTypeDefinition) => { const valueType = this.wrapperFactories.ValueType.wrap(valueTypeDefinition); if (valueType !== undefined && valueType.isReferenceableByUser()) { diff --git a/libs/language-server/src/lib/lsp/jayvee-scope-computation.ts b/libs/language-server/src/lib/lsp/jayvee-scope-computation.ts deleted file mode 100644 index 0acce4f77..000000000 --- a/libs/language-server/src/lib/lsp/jayvee-scope-computation.ts +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg -// -// SPDX-License-Identifier: AGPL-3.0-only - -import { - type AstNode, - type AstNodeDescription, - DefaultScopeComputation, - type LangiumCoreServices, - type LangiumDocument, -} from 'langium'; - -import { - isBuiltinConstrainttypeDefinition, - isConstraintDefinition, - isIotypeDefinition, - isReferenceableBlockTypeDefinition, - isTransformDefinition, - isValuetypeDefinition, -} from '../ast'; - -export class JayveeScopeComputation extends DefaultScopeComputation { - constructor(services: LangiumCoreServices) { - super(services); - } - - protected override exportNode( - node: AstNode, - exports: AstNodeDescription[], - document: LangiumDocument, - ): void { - // export the exportable top-level elements - if (!this.isExportable(node)) { - return; - } - - super.exportNode(node, exports, document); - } - - isExportable(node: AstNode) { - // pipelines are not exported - - return ( - isValuetypeDefinition(node) || - isConstraintDefinition(node) || - isTransformDefinition(node) || - isReferenceableBlockTypeDefinition(node) || - isBuiltinConstrainttypeDefinition(node) || - isIotypeDefinition(node) - ); - } -} diff --git a/libs/language-server/src/lib/lsp/jayvee-scope-provider.ts b/libs/language-server/src/lib/lsp/jayvee-scope-provider.ts index fda62b3ad..81fb7f3e7 100644 --- a/libs/language-server/src/lib/lsp/jayvee-scope-provider.ts +++ b/libs/language-server/src/lib/lsp/jayvee-scope-provider.ts @@ -2,10 +2,16 @@ // // SPDX-License-Identifier: AGPL-3.0-only +// eslint-disable-next-line unicorn/prefer-node-protocol +import { strict as assert } from 'assert'; + import { + type AstNodeDescription, AstUtils, DefaultScopeProvider, + DocumentCache, EMPTY_SCOPE, + type LangiumDocument, type LangiumDocuments, MapScope, type ReferenceInfo, @@ -13,19 +19,32 @@ import { URI, } from 'langium'; -import { type JayveeModel, isJayveeModel } from '../ast'; +import { + type ExportDefinition, + type ExportableElement, + type JayveeModel, + isExportDefinition, + isExportableElement, + isExportableElementDefinition, + isJayveeModel, +} from '../ast'; import { getStdLib } from '../builtin-library'; import { type JayveeServices } from '../jayvee-module'; import { type JayveeImportResolver } from '../services/import-resolver'; export class JayveeScopeProvider extends DefaultScopeProvider { protected readonly langiumDocuments: LangiumDocuments; - protected readonly importResover: JayveeImportResolver; + protected readonly importResolver: JayveeImportResolver; + protected readonly availableElementsPerDocumentCache: DocumentCache< + string, + ExportableElement[] + >; // DocumentCache becomes invalidated as soon the corresponding document is updated constructor(services: JayveeServices) { super(services); this.langiumDocuments = services.shared.workspace.LangiumDocuments; - this.importResover = services.ImportResolver; + this.importResolver = services.ImportResolver; + this.availableElementsPerDocumentCache = new DocumentCache(services.shared); } protected override getGlobalScope( @@ -44,18 +63,105 @@ export class JayveeScopeProvider extends DefaultScopeProvider { this.gatherImports(jayveeModel, importedUris); this.gatherBuiltins(importedUris); - const importedElements = this.indexManager.allElements( - referenceType, - importedUris, + const importedDocuments = [...importedUris].map((importedUri) => + this.langiumDocuments.getDocument(URI.parse(importedUri)), ); + const importedElements: AstNodeDescription[] = []; + for (const importedDocument of importedDocuments) { + if (importedDocument === undefined) { + continue; + } + + const publishedElements = this.availableElementsPerDocumentCache.get( + importedDocument.uri, + 'exports', // we only need one key here as it is on document basis + () => this.getExportedElements(importedDocument), + ); + importedElements.push( + ...publishedElements.map((e) => + this.descriptions.createDescription(e, e.name), + ), + ); + } + return new MapScope(importedElements); } /** - * Add all builtins URIs to @param importedUris + * Gets all exported elements from a document. + * This logic cannot reside in a {@link ScopeComputationProvider} but should be handled here: + * https://github.com/eclipse-langium/langium/discussions/1508#discussioncomment-9524544 + */ + protected getExportedElements( + document: LangiumDocument, + ): ExportableElement[] { + const model = document.parseResult.value as JayveeModel; + const exportedElements: ExportableElement[] = []; + + for (const node of AstUtils.streamAllContents(model)) { + if (isExportableElementDefinition(node) && node.isPublished) { + assert( + isExportableElement(node), + 'Exported node is not an ExportableElement', + ); + exportedElements.push(node); + } + + if (isExportDefinition(node)) { + const originalDefinition = this.followExportDefinitionChain(node); + if (originalDefinition !== undefined) { + exportedElements.push(originalDefinition); + } + } + } + return exportedElements; + } + + /** + * Follow an export statement to its original definition. + */ + protected followExportDefinitionChain( + exportDefinition: ExportDefinition, + ): ExportableElement | undefined { + const referenced = exportDefinition.element.ref; + + if (referenced === undefined) { + return undefined; // Cannot follow reference to original definition + } + + if (!this.isElementExported(referenced)) { + return undefined; + } + + return referenced; // Reached original definition + } + + /** + * Checks whether an exportable @param element is exported (either in definition or via an delayed export definition). */ - private gatherBuiltins(importedUris: Set) { + protected isElementExported(element: ExportableElement): boolean { + if (isExportableElementDefinition(element) && element.isPublished) { + return true; + } + + const model = AstUtils.getContainerOfType(element, isJayveeModel); + assert( + model !== undefined, + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + `Could not get container of exportable element ${element.name ?? ''}`, + ); + + const isExported = model.exports.some( + (exportDefinition) => exportDefinition.element.ref === element, + ); + return isExported; + } + + /** + * Add all builtins' URIs to @param importedUris + */ + protected gatherBuiltins(importedUris: Set) { const builtins = getStdLib(); const uris = Object.keys(builtins); @@ -67,14 +173,14 @@ export class JayveeScopeProvider extends DefaultScopeProvider { } /** - * Recursively add all imported URIs to @param importedUris + * Add all imported URIs of the given @jayveeModel to @param importedUris */ - private gatherImports( + protected gatherImports( jayveeModel: JayveeModel, importedUris: Set, ): void { for (const importDefinition of jayveeModel.imports) { - const uri = this.importResover.resolveImportUri(importDefinition); + const uri = this.importResolver.resolveImportUri(importDefinition); if (uri === undefined) { continue; } @@ -88,12 +194,6 @@ export class JayveeScopeProvider extends DefaultScopeProvider { if (importedDocument === undefined) { continue; } - - const rootNode = importedDocument.parseResult.value; - if (!isJayveeModel(rootNode)) { - continue; - } - this.gatherImports(rootNode, importedUris); } } } diff --git a/libs/language-server/src/lib/validation/checks/block-type-definition.spec.ts b/libs/language-server/src/lib/validation/checks/block-type-definition.spec.ts index 5ecf49868..e8b36df70 100644 --- a/libs/language-server/src/lib/validation/checks/block-type-definition.spec.ts +++ b/libs/language-server/src/lib/validation/checks/block-type-definition.spec.ts @@ -2,11 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; @@ -14,11 +10,13 @@ import { type BuiltinBlockTypeDefinition, type JayveeServices, createJayveeServices, + isBuiltinBlockTypeDefinition, } from '../..'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -34,7 +32,6 @@ describe('Validation of BuiltinBlockTypeDefinition', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -46,21 +43,23 @@ describe('Validation of BuiltinBlockTypeDefinition', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const blockType = locator.getAstNode( - document.parseResult.value, - 'blockTypes@0', - ) as BuiltinBlockTypeDefinition; - - validateBlockTypeDefinition( - blockType, - createJayveeValidationProps(validationAcceptorMock, services), + const allBlockTypes = extractTestElements( + document, + (x): x is BuiltinBlockTypeDefinition => isBuiltinBlockTypeDefinition(x), ); + + for (const blockType of allBlockTypes) { + validateBlockTypeDefinition( + blockType, + createJayveeValidationProps(validationAcceptorMock, services), + ); + } } beforeAll(() => { // Create language services services = createJayveeServices(NodeFileSystem).Jayvee; - locator = services.workspace.AstNodeLocator; + // Parse function for Jayvee (without validation) parse = parseHelper(services); }); diff --git a/libs/language-server/src/lib/validation/checks/block-type-specific/property-assignment.spec.ts b/libs/language-server/src/lib/validation/checks/block-type-specific/property-assignment.spec.ts index dec8260ad..5fd7643ce 100644 --- a/libs/language-server/src/lib/validation/checks/block-type-specific/property-assignment.spec.ts +++ b/libs/language-server/src/lib/validation/checks/block-type-specific/property-assignment.spec.ts @@ -2,11 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; @@ -16,11 +12,14 @@ import { type PropertySpecification, type TypedObjectWrapper, createJayveeServices, + isBlockDefinition, + isPropertyBody, } from '../../..'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -36,7 +35,6 @@ describe('Validation of block type specific properties', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -48,10 +46,12 @@ describe('Validation of block type specific properties', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const propertyBody = locator.getAstNode( - document.parseResult.value, - 'pipelines@0/blocks@0/body', - ) as PropertyBody; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const propertyBody = extractTestElements( + document, + (x): x is PropertyBody => + isPropertyBody(x) && isBlockDefinition(x.$container), + )[0]!; const props = createJayveeValidationProps(validationAcceptorMock, services); const wrapper = props.wrapperFactories.TypedObject.wrap( @@ -76,7 +76,7 @@ describe('Validation of block type specific properties', () => { beforeAll(() => { // Create language services services = createJayveeServices(NodeFileSystem).Jayvee; - locator = services.workspace.AstNodeLocator; + // Parse function for Jayvee (without validation) parse = parseHelper(services); }); diff --git a/libs/language-server/src/lib/validation/checks/block-type-specific/property-body.spec.ts b/libs/language-server/src/lib/validation/checks/block-type-specific/property-body.spec.ts index b1c3783ec..921e841a9 100644 --- a/libs/language-server/src/lib/validation/checks/block-type-specific/property-body.spec.ts +++ b/libs/language-server/src/lib/validation/checks/block-type-specific/property-body.spec.ts @@ -2,11 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; @@ -14,11 +10,14 @@ import { type JayveeServices, type PropertyBody, createJayveeServices, + isBlockDefinition, + isPropertyBody, } from '../../..'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -34,7 +33,6 @@ describe('Validation of block type specific property bodies', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -46,10 +44,12 @@ describe('Validation of block type specific property bodies', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const propertyBody = locator.getAstNode( - document.parseResult.value, - 'pipelines@0/blocks@0/body', - ) as PropertyBody; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const propertyBody = extractTestElements( + document, + (x): x is PropertyBody => + isPropertyBody(x) && isBlockDefinition(x.$container), + )[0]!; const props = createJayveeValidationProps(validationAcceptorMock, services); @@ -64,7 +64,6 @@ describe('Validation of block type specific property bodies', () => { beforeAll(() => { // Create language services services = createJayveeServices(NodeFileSystem).Jayvee; - locator = services.workspace.AstNodeLocator; // Parse function for Jayvee (without validation) parse = parseHelper(services); }); diff --git a/libs/language-server/src/lib/validation/checks/column-id.spec.ts b/libs/language-server/src/lib/validation/checks/column-id.spec.ts index 583e1e606..70fd6f652 100644 --- a/libs/language-server/src/lib/validation/checks/column-id.spec.ts +++ b/libs/language-server/src/lib/validation/checks/column-id.spec.ts @@ -2,11 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; @@ -14,11 +10,13 @@ import { type ColumnId, type JayveeServices, createJayveeServices, + isColumnId, } from '../../../lib'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -34,7 +32,6 @@ describe('Validation of ColumnId', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -46,21 +43,21 @@ describe('Validation of ColumnId', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const columnId = locator.getAstNode( - document.parseResult.value, - 'pipelines@0/blocks@0/body/properties@0/value/columnId', - ) as ColumnId; - - validateColumnId( - columnId, - createJayveeValidationProps(validationAcceptorMock, services), + const allColumnIds = extractTestElements(document, (x): x is ColumnId => + isColumnId(x), ); + + for (const columnId of allColumnIds) { + validateColumnId( + columnId, + createJayveeValidationProps(validationAcceptorMock, services), + ); + } } beforeAll(() => { // Create language services services = createJayveeServices(NodeFileSystem).Jayvee; - locator = services.workspace.AstNodeLocator; // Parse function for Jayvee (without validation) parse = parseHelper(services); }); diff --git a/libs/language-server/src/lib/validation/checks/composite-block-type-definition.spec.ts b/libs/language-server/src/lib/validation/checks/composite-block-type-definition.spec.ts index 5a4da6455..10715980b 100644 --- a/libs/language-server/src/lib/validation/checks/composite-block-type-definition.spec.ts +++ b/libs/language-server/src/lib/validation/checks/composite-block-type-definition.spec.ts @@ -2,11 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; @@ -14,11 +10,13 @@ import { type CompositeBlockTypeDefinition, type JayveeServices, createJayveeServices, + isCompositeBlockTypeDefinition, } from '../..'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -34,7 +32,6 @@ describe('Validation of CompositeBlockTypeDefinition', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -46,21 +43,23 @@ describe('Validation of CompositeBlockTypeDefinition', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const blockType = locator.getAstNode( - document.parseResult.value, - 'blockTypes@0', - ) as CompositeBlockTypeDefinition; - - validateCompositeBlockTypeDefinition( - blockType, - createJayveeValidationProps(validationAcceptorMock, services), + const allCompositeBlockTypes = extractTestElements( + document, + (x): x is CompositeBlockTypeDefinition => + isCompositeBlockTypeDefinition(x), ); + + for (const blockType of allCompositeBlockTypes) { + validateCompositeBlockTypeDefinition( + blockType, + createJayveeValidationProps(validationAcceptorMock, services), + ); + } } beforeAll(() => { // Create language services services = createJayveeServices(NodeFileSystem).Jayvee; - locator = services.workspace.AstNodeLocator; // Parse function for Jayvee (without validation) parse = parseHelper(services); }); diff --git a/libs/language-server/src/lib/validation/checks/constrainttype-specific/property-assignment.spec.ts b/libs/language-server/src/lib/validation/checks/constrainttype-specific/property-assignment.spec.ts index 89ec0fcc7..3bb014d06 100644 --- a/libs/language-server/src/lib/validation/checks/constrainttype-specific/property-assignment.spec.ts +++ b/libs/language-server/src/lib/validation/checks/constrainttype-specific/property-assignment.spec.ts @@ -2,11 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; @@ -16,11 +12,14 @@ import { type PropertySpecification, type TypedObjectWrapper, createJayveeServices, + isPropertyBody, + isTypedConstraintDefinition, } from '../../..'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -36,7 +35,6 @@ describe('Validation of constraint type specific properties', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -48,10 +46,12 @@ describe('Validation of constraint type specific properties', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const propertyBody = locator.getAstNode( - document.parseResult.value, - 'constraints@0/body', - ) as PropertyBody; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const propertyBody = extractTestElements( + document, + (x): x is PropertyBody => + isPropertyBody(x) && isTypedConstraintDefinition(x.$container), + )[0]!; const props = createJayveeValidationProps(validationAcceptorMock, services); @@ -78,8 +78,6 @@ describe('Validation of constraint type specific properties', () => { // Create language services services = createJayveeServices(NodeFileSystem).Jayvee; - locator = services.workspace.AstNodeLocator; - // Parse function for Jayvee (without validation) parse = parseHelper(services); }); diff --git a/libs/language-server/src/lib/validation/checks/constrainttype-specific/property-body.spec.ts b/libs/language-server/src/lib/validation/checks/constrainttype-specific/property-body.spec.ts index 8ef5ab0a3..0c59a693d 100644 --- a/libs/language-server/src/lib/validation/checks/constrainttype-specific/property-body.spec.ts +++ b/libs/language-server/src/lib/validation/checks/constrainttype-specific/property-body.spec.ts @@ -2,23 +2,21 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; import { type JayveeServices, - type PropertyBody, + type TypedConstraintDefinition, createJayveeServices, + isTypedConstraintDefinition, } from '../../..'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -34,7 +32,6 @@ describe('Validation of constraint type specific property bodies', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -46,27 +43,31 @@ describe('Validation of constraint type specific property bodies', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const propertyBody = locator.getAstNode( - document.parseResult.value, - 'constraints@0/body', - ) as PropertyBody; + const allTypedConstraints = extractTestElements( + document, + (x): x is TypedConstraintDefinition => isTypedConstraintDefinition(x), + ); - const props = createJayveeValidationProps(validationAcceptorMock, services); + for (const constraint of allTypedConstraints) { + const propertyBody = constraint.body; + const props = createJayveeValidationProps( + validationAcceptorMock, + services, + ); - const wrapper = props.wrapperFactories.TypedObject.wrap( - propertyBody.$container.type, - ); - expect(wrapper).toBeDefined(); + const wrapper = props.wrapperFactories.TypedObject.wrap( + propertyBody.$container.type, + ); + expect(wrapper).toBeDefined(); - checkConstraintTypeSpecificPropertyBody(propertyBody, props); + checkConstraintTypeSpecificPropertyBody(propertyBody, props); + } } beforeAll(() => { // Create language services services = createJayveeServices(NodeFileSystem).Jayvee; - locator = services.workspace.AstNodeLocator; - // Parse function for Jayvee (without validation) parse = parseHelper(services); }); diff --git a/libs/language-server/src/lib/validation/checks/expression-constraint-definition.spec.ts b/libs/language-server/src/lib/validation/checks/expression-constraint-definition.spec.ts index a05aba331..29a3ce454 100644 --- a/libs/language-server/src/lib/validation/checks/expression-constraint-definition.spec.ts +++ b/libs/language-server/src/lib/validation/checks/expression-constraint-definition.spec.ts @@ -2,11 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; @@ -14,11 +10,13 @@ import { type ExpressionConstraintDefinition, type JayveeServices, createJayveeServices, + isExpressionConstraintDefinition, } from '../../../lib'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -34,7 +32,6 @@ describe('Validation of ConstraintDefinition (expression syntax)', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -46,22 +43,24 @@ describe('Validation of ConstraintDefinition (expression syntax)', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const expressionConstraint = - locator.getAstNode( - document.parseResult.value, - 'constraints@0', - ) as ExpressionConstraintDefinition; - - validateExpressionConstraintDefinition( - expressionConstraint, - createJayveeValidationProps(validationAcceptorMock, services), + const allExpressionConstraintDefinitions = extractTestElements( + document, + (x): x is ExpressionConstraintDefinition => + isExpressionConstraintDefinition(x), ); + + for (const expressionConstraint of allExpressionConstraintDefinitions) { + validateExpressionConstraintDefinition( + expressionConstraint, + createJayveeValidationProps(validationAcceptorMock, services), + ); + } } beforeAll(() => { // Create language services services = createJayveeServices(NodeFileSystem).Jayvee; - locator = services.workspace.AstNodeLocator; + // Parse function for Jayvee (without validation) parse = parseHelper(services); }); diff --git a/libs/language-server/src/lib/validation/checks/import-definition.spec.ts b/libs/language-server/src/lib/validation/checks/import-definition.spec.ts index 7f9e63f72..cb2e0dd75 100644 --- a/libs/language-server/src/lib/validation/checks/import-definition.spec.ts +++ b/libs/language-server/src/lib/validation/checks/import-definition.spec.ts @@ -131,5 +131,65 @@ describe('Validation of ImportDefinition', () => { expect.any(Object), ); }); + + it('should diagnose error on cyclic import', async () => { + const relativeTestFilePath = + 'import-definition/invalid-dependency-cycle-1.jv'; + + await parseAndValidateImportDefinition(relativeTestFilePath); + + expect(validationAcceptorMock).toHaveBeenCalledTimes(1); + expect(validationAcceptorMock).toHaveBeenNthCalledWith( + 1, + 'error', + 'Import from "./invalid-dependency-cycle-2.jv" leads to import cycle: "./invalid-dependency-cycle-2.jv" -> "./invalid-dependency-cycle-1.jv" -> "./invalid-dependency-cycle-2.jv"', + expect.any(Object), + ); + }); + + it('should diagnose error on cyclic import when importing itself', async () => { + const relativeTestFilePath = + 'import-definition/invalid-dependency-cycle-self-import.jv'; + + await parseAndValidateImportDefinition(relativeTestFilePath); + + expect(validationAcceptorMock).toHaveBeenCalledTimes(1); + expect(validationAcceptorMock).toHaveBeenNthCalledWith( + 1, + 'error', + 'Import from "./invalid-dependency-cycle-self-import.jv" leads to import cycle: "./invalid-dependency-cycle-self-import.jv" -> "./invalid-dependency-cycle-self-import.jv"', + expect.any(Object), + ); + }); + + it('should diagnose error on cyclic import even when cycle resides in imported file', async () => { + const relativeTestFilePath = + 'import-definition/invalid-dependency-cycle-deeper-1.jv'; + + await parseAndValidateImportDefinition(relativeTestFilePath); + + expect(validationAcceptorMock).toHaveBeenCalledTimes(1); + expect(validationAcceptorMock).toHaveBeenNthCalledWith( + 1, + 'error', + 'Import from "./invalid-dependency-cycle-deeper-2.jv" leads to import cycle: "./invalid-dependency-cycle-deeper-2.jv" -> "./invalid-dependency-cycle-deeper-3.jv" -> "./invalid-dependency-cycle-deeper-2.jv"', + expect.any(Object), + ); + }); + + it('should diagnose error on cyclic import spans multiple files', async () => { + const relativeTestFilePath = + 'import-definition/invalid-dependency-cycle-transitive-1.jv'; + + await parseAndValidateImportDefinition(relativeTestFilePath); + + expect(validationAcceptorMock).toHaveBeenCalledTimes(1); + expect(validationAcceptorMock).toHaveBeenNthCalledWith( + 1, + 'error', + 'Import from "./invalid-dependency-cycle-transitive-2.jv" leads to import cycle: "./invalid-dependency-cycle-transitive-2.jv" -> "./invalid-dependency-cycle-transitive-3.jv" -> "./invalid-dependency-cycle-transitive-1.jv" -> "./invalid-dependency-cycle-transitive-2.jv"', + expect.any(Object), + ); + }); }); }); diff --git a/libs/language-server/src/lib/validation/checks/import-definition.ts b/libs/language-server/src/lib/validation/checks/import-definition.ts index 106169ca9..cac0ba6f7 100644 --- a/libs/language-server/src/lib/validation/checks/import-definition.ts +++ b/libs/language-server/src/lib/validation/checks/import-definition.ts @@ -2,6 +2,9 @@ // // SPDX-License-Identifier: AGPL-3.0-only +// eslint-disable-next-line unicorn/prefer-node-protocol +import { strict as assert } from 'assert'; + import { type ImportDefinition } from '../../ast/generated/ast'; import { type JayveeValidationProps } from '../validation-registry'; @@ -10,6 +13,11 @@ export function validateImportDefinition( props: JayveeValidationProps, ): void { checkPathExists(importDefinition, props); + if (props.validationContext.hasErrorOccurred()) { + return; + } + + checkCyclicImportChain(importDefinition, props); } function checkPathExists( @@ -28,3 +36,85 @@ function checkPathExists( ); } } + +function checkCyclicImportChain( + importDefinition: ImportDefinition, + props: JayveeValidationProps, +): void { + const cycleAnalysisTraces = analyzeImportChain( + importDefinition, + new Set(), + props, + ); + + const cycles = cycleAnalysisTraces.filter((x) => x.isCyclic); + + if (cycles.length > 0) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const shownImportChain = cycles[0]!.path + .map((x) => `"${x.path}"`) + .join(' -> '); + props.validationContext.accept( + 'error', + `Import from "${importDefinition.path}" leads to import cycle: ${shownImportChain}`, + { + node: importDefinition, + }, + ); + } +} + +interface ImportChainTrace { + isCyclic: boolean; + path: ImportDefinition[]; +} + +function analyzeImportChain( + importDefinition: ImportDefinition, + visitedDocumentUris: Set, + props: JayveeValidationProps, +): ImportChainTrace[] { + const importedModel = props.importResolver.resolveImport(importDefinition); + if (importedModel === undefined) { + return [ + { + isCyclic: false, + path: [importDefinition], + }, + ]; + } + + const currentVisitedUri = importedModel.$document?.uri.toString(); + assert( + currentVisitedUri !== undefined, + 'Could not resolve URI of imported document', + ); + + if (visitedDocumentUris.has(currentVisitedUri)) { + // cycle detected + return [ + { + isCyclic: true, + path: [importDefinition], + }, + ]; + } + visitedDocumentUris.add(currentVisitedUri); + + const cycledAnalysisResult: ImportChainTrace[] = []; + for (const furtherImport of importedModel.imports) { + const downwardAnalysisResult = analyzeImportChain( + furtherImport, + visitedDocumentUris, + props, + ); + const addedThisNodeToPath = downwardAnalysisResult.map((r) => { + return { + isCyclic: r.isCyclic, + path: [importDefinition, ...r.path], + }; + }); + cycledAnalysisResult.push(...addedThisNodeToPath); + } + return cycledAnalysisResult; +} diff --git a/libs/language-server/src/lib/validation/checks/jayvee-model.ts b/libs/language-server/src/lib/validation/checks/jayvee-model.ts index df136b073..dd945f06f 100644 --- a/libs/language-server/src/lib/validation/checks/jayvee-model.ts +++ b/libs/language-server/src/lib/validation/checks/jayvee-model.ts @@ -2,7 +2,18 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { type JayveeModel } from '../../ast/generated/ast'; +import { AstUtils } from 'langium'; + +import { + type JayveeModel, + isBuiltinConstrainttypeDefinition, + isConstraintDefinition, + isIotypeDefinition, + isPipelineDefinition, + isReferenceableBlockTypeDefinition, + isTransformDefinition, + isValuetypeDefinition, +} from '../../ast/generated/ast'; import { type JayveeValidationProps } from '../validation-registry'; import { checkUniqueNames } from '../validation-util'; @@ -10,11 +21,34 @@ export function validateJayveeModel( model: JayveeModel, props: JayveeValidationProps, ): void { - checkUniqueNames(model.pipelines, props.validationContext); - checkUniqueNames(model.transforms, props.validationContext); - checkUniqueNames(model.valueTypes, props.validationContext); - checkUniqueNames(model.constraints, props.validationContext); - checkUniqueNames(model.blockTypes, props.validationContext); - checkUniqueNames(model.constrainttypes, props.validationContext); - checkUniqueNames(model.iotypes, props.validationContext); + const allElements = AstUtils.streamAllContents(model); + + checkUniqueNames( + [...allElements.filter(isPipelineDefinition)], + props.validationContext, + ); + checkUniqueNames( + [...allElements.filter(isTransformDefinition)], + props.validationContext, + ); + checkUniqueNames( + [...allElements.filter(isValuetypeDefinition)], + props.validationContext, + ); + checkUniqueNames( + [...allElements.filter(isConstraintDefinition)], + props.validationContext, + ); + checkUniqueNames( + [...allElements.filter(isReferenceableBlockTypeDefinition)], + props.validationContext, + ); + checkUniqueNames( + [...allElements.filter(isBuiltinConstrainttypeDefinition)], + props.validationContext, + ); + checkUniqueNames( + [...allElements.filter(isIotypeDefinition)], + props.validationContext, + ); } diff --git a/libs/language-server/src/lib/validation/checks/pipe-definition.spec.ts b/libs/language-server/src/lib/validation/checks/pipe-definition.spec.ts index d884298b6..f93a3bcc9 100644 --- a/libs/language-server/src/lib/validation/checks/pipe-definition.spec.ts +++ b/libs/language-server/src/lib/validation/checks/pipe-definition.spec.ts @@ -2,11 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; @@ -14,11 +10,13 @@ import { type JayveeServices, type PipeDefinition, createJayveeServices, + isPipeDefinition, } from '../../../lib'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -34,7 +32,6 @@ describe('Validation of PipeDefinition', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -46,21 +43,21 @@ describe('Validation of PipeDefinition', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const pipe = locator.getAstNode( - document.parseResult.value, - 'pipelines@0/pipes@0', - ) as PipeDefinition; - - validatePipeDefinition( - pipe, - createJayveeValidationProps(validationAcceptorMock, services), + const allPipes = extractTestElements(document, (x): x is PipeDefinition => + isPipeDefinition(x), ); + + for (const pipe of allPipes) { + validatePipeDefinition( + pipe, + createJayveeValidationProps(validationAcceptorMock, services), + ); + } } beforeAll(() => { // Create language services services = createJayveeServices(NodeFileSystem).Jayvee; - locator = services.workspace.AstNodeLocator; // Parse function for Jayvee (without validation) parse = parseHelper(services); }); 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 71cd94718..4538d309e 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 @@ -2,11 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; @@ -14,11 +10,13 @@ import { type JayveeServices, type PipelineDefinition, createJayveeServices, + isPipelineDefinition, } from '../../../lib'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -34,7 +32,6 @@ describe('Validation of PipelineDefinition', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -46,21 +43,23 @@ describe('Validation of PipelineDefinition', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const pipeline = locator.getAstNode( - document.parseResult.value, - 'pipelines@0', - ) as PipelineDefinition; - - validatePipelineDefinition( - pipeline, - createJayveeValidationProps(validationAcceptorMock, services), + const allPipelines = extractTestElements( + document, + (x): x is PipelineDefinition => isPipelineDefinition(x), ); + + for (const pipeline of allPipelines) { + validatePipelineDefinition( + pipeline, + createJayveeValidationProps(validationAcceptorMock, services), + ); + } } beforeAll(() => { // Create language services services = createJayveeServices(NodeFileSystem).Jayvee; - locator = services.workspace.AstNodeLocator; + // Parse function for Jayvee (without validation) parse = parseHelper(services); }); diff --git a/libs/language-server/src/lib/validation/checks/property-assignment.spec.ts b/libs/language-server/src/lib/validation/checks/property-assignment.spec.ts index 08b3e6649..320f4625f 100644 --- a/libs/language-server/src/lib/validation/checks/property-assignment.spec.ts +++ b/libs/language-server/src/lib/validation/checks/property-assignment.spec.ts @@ -2,25 +2,23 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; import { type JayveeServices, - type PropertyAssignment, type PropertyBody, type TypedObjectWrapper, createJayveeServices, + isBlockDefinition, + isPropertyBody, } from '../../../lib'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -36,7 +34,6 @@ describe('Validation of PropertyAssignment', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -48,33 +45,40 @@ describe('Validation of PropertyAssignment', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const propertyBody = locator.getAstNode( - document.parseResult.value, - 'pipelines@0/blocks@0/body', - ) as PropertyBody; + const allPropertyBodies = extractTestElements( + document, + (x): x is PropertyBody => + isPropertyBody(x) && isBlockDefinition(x.$container), + ); - const type = propertyBody.$container.type; + for (const propertyBody of allPropertyBodies) { + const type = propertyBody.$container.type; - const props = createJayveeValidationProps(validationAcceptorMock, services); - const wrapper = props.wrapperFactories.TypedObject.wrap(type); - expect(wrapper).toBeDefined(); + const props = createJayveeValidationProps( + validationAcceptorMock, + services, + ); + const wrapper = props.wrapperFactories.TypedObject.wrap(type); + expect(wrapper).toBeDefined(); - const propertyAssignment = locator.getAstNode( - propertyBody, - 'properties@0', - ) as PropertyAssignment; + const propertyAssignments = propertyBody.properties; + expect( + propertyAssignments.length > 0, + 'No property assignment found in test file', + ); - validatePropertyAssignment( - propertyAssignment, - wrapper as TypedObjectWrapper, - props, - ); + validatePropertyAssignment( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + propertyAssignments[0]!, + wrapper as TypedObjectWrapper, + props, + ); + } } beforeAll(() => { // Create language services services = createJayveeServices(NodeFileSystem).Jayvee; - locator = services.workspace.AstNodeLocator; // Parse function for Jayvee (without validation) parse = parseHelper(services); }); diff --git a/libs/language-server/src/lib/validation/checks/property-body.spec.ts b/libs/language-server/src/lib/validation/checks/property-body.spec.ts index 215d416bd..446c64420 100644 --- a/libs/language-server/src/lib/validation/checks/property-body.spec.ts +++ b/libs/language-server/src/lib/validation/checks/property-body.spec.ts @@ -2,11 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; @@ -14,11 +10,14 @@ import { type JayveeServices, type PropertyBody, createJayveeServices, + isBlockDefinition, + isPropertyBody, } from '../../../lib'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -34,7 +33,6 @@ describe('Validation PropertyBody', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -46,21 +44,24 @@ describe('Validation PropertyBody', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const propertyBody = locator.getAstNode( - document.parseResult.value, - 'pipelines@0/blocks@0/body', - ) as PropertyBody; - - validatePropertyBody( - propertyBody, - createJayveeValidationProps(validationAcceptorMock, services), + const allPropertyBodies = extractTestElements( + document, + (x): x is PropertyBody => + isPropertyBody(x) && isBlockDefinition(x.$container), ); + + for (const propertyBody of allPropertyBodies) { + validatePropertyBody( + propertyBody, + createJayveeValidationProps(validationAcceptorMock, services), + ); + } } beforeAll(() => { // Create language services services = createJayveeServices(NodeFileSystem).Jayvee; - locator = services.workspace.AstNodeLocator; + // Parse function for Jayvee (without validation) parse = parseHelper(services); }); diff --git a/libs/language-server/src/lib/validation/checks/range-literal.spec.ts b/libs/language-server/src/lib/validation/checks/range-literal.spec.ts index 175a5f216..68713b9af 100644 --- a/libs/language-server/src/lib/validation/checks/range-literal.spec.ts +++ b/libs/language-server/src/lib/validation/checks/range-literal.spec.ts @@ -2,11 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; @@ -14,11 +10,13 @@ import { type JayveeServices, type RangeLiteral, createJayveeServices, + isRangeLiteral, } from '../../../lib'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -34,7 +32,6 @@ describe('Validation of RangeLiteral', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -46,21 +43,23 @@ describe('Validation of RangeLiteral', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const rangeLiteral = locator.getAstNode( - document.parseResult.value, - 'pipelines@0/blocks@0/body/properties@0/value', - ) as RangeLiteral; - - validateRangeLiteral( - rangeLiteral, - createJayveeValidationProps(validationAcceptorMock, services), + const allRangeLiterals = extractTestElements( + document, + (x): x is RangeLiteral => isRangeLiteral(x), ); + + for (const rangeLiteral of allRangeLiterals) { + validateRangeLiteral( + rangeLiteral, + createJayveeValidationProps(validationAcceptorMock, services), + ); + } } beforeAll(() => { // Create language services services = createJayveeServices(NodeFileSystem).Jayvee; - locator = services.workspace.AstNodeLocator; + // Parse function for Jayvee (without validation) parse = parseHelper(services); }); diff --git a/libs/language-server/src/lib/validation/checks/regex-literal.spec.ts b/libs/language-server/src/lib/validation/checks/regex-literal.spec.ts index 81561f705..84b10eac5 100644 --- a/libs/language-server/src/lib/validation/checks/regex-literal.spec.ts +++ b/libs/language-server/src/lib/validation/checks/regex-literal.spec.ts @@ -2,11 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; @@ -14,11 +10,13 @@ import { type JayveeServices, type RegexLiteral, createJayveeServices, + isRegexLiteral, } from '../../../lib'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -34,7 +32,6 @@ describe('Validation of RegexLiteral', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -46,21 +43,23 @@ describe('Validation of RegexLiteral', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const regexLiteral = locator.getAstNode( - document.parseResult.value, - 'pipelines@0/blocks@0/body/properties@0/value', - ) as RegexLiteral; - - validateRegexLiteral( - regexLiteral, - createJayveeValidationProps(validationAcceptorMock, services), + const allRegexLiterals = extractTestElements( + document, + (x): x is RegexLiteral => isRegexLiteral(x), ); + + for (const regexLiteral of allRegexLiterals) { + validateRegexLiteral( + regexLiteral, + createJayveeValidationProps(validationAcceptorMock, services), + ); + } } beforeAll(() => { // Create language services services = createJayveeServices(NodeFileSystem).Jayvee; - locator = services.workspace.AstNodeLocator; + // Parse function for Jayvee (without validation) parse = parseHelper(services); }); diff --git a/libs/language-server/src/lib/validation/checks/transform-body.spec.ts b/libs/language-server/src/lib/validation/checks/transform-body.spec.ts index e1ce55e9f..f7332ef96 100644 --- a/libs/language-server/src/lib/validation/checks/transform-body.spec.ts +++ b/libs/language-server/src/lib/validation/checks/transform-body.spec.ts @@ -2,11 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; @@ -14,11 +10,13 @@ import { type JayveeServices, type TransformBody, createJayveeServices, + isTransformBody, } from '../../../lib'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -34,7 +32,6 @@ describe('Validation of TransformBody', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -46,21 +43,23 @@ describe('Validation of TransformBody', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const transformBody = locator.getAstNode( - document.parseResult.value, - 'transforms@0/body', - ) as TransformBody; - - validateTransformBody( - transformBody, - createJayveeValidationProps(validationAcceptorMock, services), + const allTransformBodies = extractTestElements( + document, + (x): x is TransformBody => isTransformBody(x), ); + + for (const transformBody of allTransformBodies) { + validateTransformBody( + transformBody, + createJayveeValidationProps(validationAcceptorMock, services), + ); + } } beforeAll(() => { // Create language services services = createJayveeServices(NodeFileSystem).Jayvee; - locator = services.workspace.AstNodeLocator; + // Parse function for Jayvee (without validation) parse = parseHelper(services); }); diff --git a/libs/language-server/src/lib/validation/checks/transform-output-assignment.spec.ts b/libs/language-server/src/lib/validation/checks/transform-output-assignment.spec.ts index 03e0e89c8..064d6e91c 100644 --- a/libs/language-server/src/lib/validation/checks/transform-output-assignment.spec.ts +++ b/libs/language-server/src/lib/validation/checks/transform-output-assignment.spec.ts @@ -2,11 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; @@ -14,11 +10,13 @@ import { type JayveeServices, type TransformOutputAssignment, createJayveeServices, + isTransformOutputAssignment, } from '../../../lib'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -34,7 +32,6 @@ describe('Validation of TransformOutputAssignment', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -46,22 +43,23 @@ describe('Validation of TransformOutputAssignment', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const transformOutputAssignment = - locator.getAstNode( - document.parseResult.value, - 'transforms@0/body/outputAssignments@0', - ) as TransformOutputAssignment; - - validateTransformOutputAssignment( - transformOutputAssignment, - createJayveeValidationProps(validationAcceptorMock, services), + const allOutputAssignments = extractTestElements( + document, + (x): x is TransformOutputAssignment => isTransformOutputAssignment(x), ); + + for (const transformOutputAssignment of allOutputAssignments) { + validateTransformOutputAssignment( + transformOutputAssignment, + createJayveeValidationProps(validationAcceptorMock, services), + ); + } } beforeAll(() => { // Create language services services = createJayveeServices(NodeFileSystem).Jayvee; - locator = services.workspace.AstNodeLocator; + // Parse function for Jayvee (without validation) parse = parseHelper(services); }); diff --git a/libs/language-server/src/lib/validation/checks/typed-constraint-definition.spec.ts b/libs/language-server/src/lib/validation/checks/typed-constraint-definition.spec.ts index fbf314963..2391cc23e 100644 --- a/libs/language-server/src/lib/validation/checks/typed-constraint-definition.spec.ts +++ b/libs/language-server/src/lib/validation/checks/typed-constraint-definition.spec.ts @@ -2,11 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; @@ -15,11 +11,13 @@ import { type TypedConstraintDefinition, createJayveeServices, initializeWorkspace, + isTypedConstraintDefinition, } from '../../../lib'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -35,7 +33,6 @@ describe('Validation of ConstraintDefinition (typed syntax)', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -47,15 +44,17 @@ describe('Validation of ConstraintDefinition (typed syntax)', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const typedConstraint = locator.getAstNode( - document.parseResult.value, - 'constraints@0', - ) as TypedConstraintDefinition; - - validateTypedConstraintDefinition( - typedConstraint, - createJayveeValidationProps(validationAcceptorMock, services), + const allTypedConstraints = extractTestElements( + document, + (x): x is TypedConstraintDefinition => isTypedConstraintDefinition(x), ); + + for (const typedConstraint of allTypedConstraints) { + validateTypedConstraintDefinition( + typedConstraint, + createJayveeValidationProps(validationAcceptorMock, services), + ); + } } beforeAll(async () => { @@ -63,7 +62,6 @@ describe('Validation of ConstraintDefinition (typed syntax)', () => { services = createJayveeServices(NodeFileSystem).Jayvee; await initializeWorkspace(services); - locator = services.workspace.AstNodeLocator; // Parse function for Jayvee (without validation) parse = parseHelper(services); }); diff --git a/libs/language-server/src/lib/validation/checks/value-type-definition.spec.ts b/libs/language-server/src/lib/validation/checks/value-type-definition.spec.ts index ba6d52964..067e42df1 100644 --- a/libs/language-server/src/lib/validation/checks/value-type-definition.spec.ts +++ b/libs/language-server/src/lib/validation/checks/value-type-definition.spec.ts @@ -2,11 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { - type AstNode, - type AstNodeLocator, - type LangiumDocument, -} from 'langium'; +import { type AstNode, type LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import { vi } from 'vitest'; @@ -14,11 +10,13 @@ import { type JayveeServices, type ValuetypeDefinition, createJayveeServices, + isValuetypeDefinition, } from '../../../lib'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -34,7 +32,6 @@ describe('Validation of ValuetypeDefinition', () => { const validationAcceptorMock = vi.fn(validationAcceptorMockImpl); - let locator: AstNodeLocator; let services: JayveeServices; const readJvTestAsset = readJvTestAssetHelper( @@ -46,21 +43,22 @@ describe('Validation of ValuetypeDefinition', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const valueTypeDefinition = locator.getAstNode( - document.parseResult.value, - 'valueTypes@0', - ) as ValuetypeDefinition; - - validateValueTypeDefinition( - valueTypeDefinition, - createJayveeValidationProps(validationAcceptorMock, services), + const allValueTypes = extractTestElements( + document, + (x): x is ValuetypeDefinition => isValuetypeDefinition(x), ); + + for (const valueTypeDefinition of allValueTypes) { + validateValueTypeDefinition( + valueTypeDefinition, + createJayveeValidationProps(validationAcceptorMock, services), + ); + } } beforeAll(() => { // Create language services services = createJayveeServices(NodeFileSystem).Jayvee; - locator = services.workspace.AstNodeLocator; // Parse function for Jayvee (without validation) parse = parseHelper(services); }); diff --git a/libs/language-server/src/lib/validation/checks/value-type-reference.spec.ts b/libs/language-server/src/lib/validation/checks/value-type-reference.spec.ts index 96f5fd49a..16e8370ff 100644 --- a/libs/language-server/src/lib/validation/checks/value-type-reference.spec.ts +++ b/libs/language-server/src/lib/validation/checks/value-type-reference.spec.ts @@ -15,14 +15,18 @@ import { vi } from 'vitest'; import { type JayveeServices, + type ReferenceableBlockTypeDefinition, type ValueTypeReference, type ValuetypeDefinition, createJayveeServices, + isReferenceableBlockTypeDefinition, + isValuetypeDefinition, } from '../..'; import { type ParseHelperOptions, createJayveeValidationProps, expectNoParserAndLexerErrors, + extractTestElements, parseHelper, readJvTestAssetHelper, validationAcceptorMockImpl, @@ -50,22 +54,20 @@ describe('Validation of ValueTypeReference', () => { const document = await parse(input); expectNoParserAndLexerErrors(document); - const valueTypeReferences: ValueTypeReference[] = []; + const allValueTypes = extractTestElements( + document, + (x): x is ValuetypeDefinition => isValuetypeDefinition(x), + ); - let valueTypeDefinition: ValuetypeDefinition | undefined; - let i = 0; - do { - valueTypeDefinition = locator.getAstNode( - document.parseResult.value, - `valueTypes@${i}`, - ); - if (valueTypeDefinition !== undefined) { - const valueTypeRef = valueTypeDefinition.type; - assert(valueTypeRef !== undefined); - valueTypeReferences.push(valueTypeRef); - } - ++i; - } while (valueTypeDefinition !== undefined); + const valueTypeReferences: ValueTypeReference[] = []; + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < allValueTypes.length; ++i) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const valueTypeDefinition = allValueTypes[i]!; + const valueTypeRef = valueTypeDefinition.type; + assert(valueTypeRef !== undefined); + valueTypeReferences.push(valueTypeRef); + } return valueTypeReferences; } @@ -284,10 +286,15 @@ describe('Validation of ValueTypeReference', () => { const document = await parse(text); expectNoParserAndLexerErrors(document); - const valueTypeRef = locator.getAstNode( - document.parseResult.value, - `blockTypes@0/properties@0/valueType`, - ); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const blockType = extractTestElements( + document, + (x): x is ReferenceableBlockTypeDefinition => + isReferenceableBlockTypeDefinition(x), + )[0]!; + + const valueTypeRef = blockType.properties[0]?.valueType; assert(valueTypeRef !== undefined); validateValueTypeReference( diff --git a/libs/language-server/src/lib/validation/validation-utils.spec.ts b/libs/language-server/src/lib/validation/validation-utils.spec.ts index 093d3053a..291e42fca 100644 --- a/libs/language-server/src/lib/validation/validation-utils.spec.ts +++ b/libs/language-server/src/lib/validation/validation-utils.spec.ts @@ -87,7 +87,7 @@ describe('Validation of validation-utils', () => { new DefaultOperatorTypeComputerRegistry( valueTypeProvider, new WrapperFactoryProvider( - new DefaultOperatorEvaluatorRegistry(), + new DefaultOperatorEvaluatorRegistry(valueTypeProvider), valueTypeProvider, ), ), @@ -113,7 +113,7 @@ describe('Validation of validation-utils', () => { new DefaultOperatorTypeComputerRegistry( valueTypeProvider, new WrapperFactoryProvider( - new DefaultOperatorEvaluatorRegistry(), + new DefaultOperatorEvaluatorRegistry(valueTypeProvider), valueTypeProvider, ), ), diff --git a/libs/language-server/src/stdlib/CSVExtractor.jv b/libs/language-server/src/stdlib/CSVExtractor.jv index 066a1bbff..c4b1557f6 100644 --- a/libs/language-server/src/stdlib/CSVExtractor.jv +++ b/libs/language-server/src/stdlib/CSVExtractor.jv @@ -5,7 +5,7 @@ /** * A CSVExtractor extracts a file from a given URL and interprets it as CSV */ -composite blocktype CSVExtractor { +publish composite blocktype CSVExtractor { property url oftype text; property delimiter oftype text: ','; property enclosing oftype text: ''; diff --git a/libs/language-server/src/stdlib/CSVFileInterpreter.jv b/libs/language-server/src/stdlib/CSVFileInterpreter.jv index 4bedc3ef6..4d7521b98 100644 --- a/libs/language-server/src/stdlib/CSVFileInterpreter.jv +++ b/libs/language-server/src/stdlib/CSVFileInterpreter.jv @@ -5,7 +5,7 @@ /** * A CSVFileInterpreter interprets a file as CSV */ -composite blocktype CSVFileInterpreter { +publish composite blocktype CSVFileInterpreter { property delimiter oftype text: ','; property enclosing oftype text: ''; property enclosingEscape oftype text: ''; diff --git a/libs/language-server/src/stdlib/builtin-block-types/ArchiveInterpreter.jv b/libs/language-server/src/stdlib/builtin-block-types/ArchiveInterpreter.jv index d490c5a76..f7e1ba78d 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/ArchiveInterpreter.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/ArchiveInterpreter.jv @@ -10,7 +10,7 @@ * archiveType: "zip"; * } */ -builtin blocktype ArchiveInterpreter { +publish builtin blocktype ArchiveInterpreter { input default oftype File; output default oftype FileSystem; diff --git a/libs/language-server/src/stdlib/builtin-block-types/CellRangeSelector.jv b/libs/language-server/src/stdlib/builtin-block-types/CellRangeSelector.jv index 87182fa4c..04cd78a14 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/CellRangeSelector.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/CellRangeSelector.jv @@ -10,7 +10,7 @@ * select: range A1:E*; * } */ -builtin blocktype CellRangeSelector { +publish builtin blocktype CellRangeSelector { input default oftype Sheet; output default oftype Sheet; diff --git a/libs/language-server/src/stdlib/builtin-block-types/CellWriter.jv b/libs/language-server/src/stdlib/builtin-block-types/CellWriter.jv index 6ff64a4e1..f2c26101e 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/CellWriter.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/CellWriter.jv @@ -17,7 +17,7 @@ * write: ["Name", "Age"]; * } */ -builtin blocktype CellWriter { +publish builtin blocktype CellWriter { input default oftype Sheet; output default oftype Sheet; diff --git a/libs/language-server/src/stdlib/builtin-block-types/ColumnDeleter.jv b/libs/language-server/src/stdlib/builtin-block-types/ColumnDeleter.jv index 1c3f18ec6..a4acd7d97 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/ColumnDeleter.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/ColumnDeleter.jv @@ -10,7 +10,7 @@ * delete: [column B]; * } */ -builtin blocktype ColumnDeleter { +publish builtin blocktype ColumnDeleter { input default oftype Sheet; output default oftype Sheet; diff --git a/libs/language-server/src/stdlib/builtin-block-types/CsvInterpreter.jv b/libs/language-server/src/stdlib/builtin-block-types/CsvInterpreter.jv index fb654e4c3..911db64a3 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/CsvInterpreter.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/CsvInterpreter.jv @@ -10,7 +10,7 @@ * delimiter: ";"; * } */ -builtin blocktype CSVInterpreter { +publish builtin blocktype CSVInterpreter { input default oftype TextFile; output default oftype Sheet; diff --git a/libs/language-server/src/stdlib/builtin-block-types/FilePicker.jv b/libs/language-server/src/stdlib/builtin-block-types/FilePicker.jv index 0b0cb8093..bd7f62217 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/FilePicker.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/FilePicker.jv @@ -10,7 +10,7 @@ * path: "./agency.txt"; * } */ -builtin blocktype FilePicker { +publish builtin blocktype FilePicker { input default oftype FileSystem; output default oftype File; diff --git a/libs/language-server/src/stdlib/builtin-block-types/GtfsRtInterpreter.jv b/libs/language-server/src/stdlib/builtin-block-types/GtfsRtInterpreter.jv index 9caf39017..df0f4de78 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/GtfsRtInterpreter.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/GtfsRtInterpreter.jv @@ -10,7 +10,7 @@ * entity: "trip_update"; * } */ -builtin blocktype GtfsRTInterpreter { +publish builtin blocktype GtfsRTInterpreter { input default oftype File; output default oftype Sheet; diff --git a/libs/language-server/src/stdlib/builtin-block-types/HttpExtractor.jv b/libs/language-server/src/stdlib/builtin-block-types/HttpExtractor.jv index 014328c96..a85f7bcba 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/HttpExtractor.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/HttpExtractor.jv @@ -10,7 +10,7 @@ * url: "tinyurl.com/4ub9spwz"; * } */ -builtin blocktype HttpExtractor { +publish builtin blocktype HttpExtractor { input default oftype None; output default oftype File; diff --git a/libs/language-server/src/stdlib/builtin-block-types/LocalFileExtractor.jv b/libs/language-server/src/stdlib/builtin-block-types/LocalFileExtractor.jv index a4f7a3c5b..0221e6560 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/LocalFileExtractor.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/LocalFileExtractor.jv @@ -10,7 +10,7 @@ * filePath: "cars.csv"; * } */ -builtin blocktype LocalFileExtractor { +publish builtin blocktype LocalFileExtractor { input default oftype None; output default oftype File; diff --git a/libs/language-server/src/stdlib/builtin-block-types/PostgresLoader.jv b/libs/language-server/src/stdlib/builtin-block-types/PostgresLoader.jv index 2b2a824ab..1d37d8ded 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/PostgresLoader.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/PostgresLoader.jv @@ -15,7 +15,7 @@ * table: "Cars"; * } */ -builtin blocktype PostgresLoader { +publish builtin blocktype PostgresLoader { input default oftype Table; output default oftype None; diff --git a/libs/language-server/src/stdlib/builtin-block-types/RowDeleter.jv b/libs/language-server/src/stdlib/builtin-block-types/RowDeleter.jv index 9835a2326..79211a05d 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/RowDeleter.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/RowDeleter.jv @@ -10,7 +10,7 @@ * delete: [row 2]; * } */ -builtin blocktype RowDeleter { +publish builtin blocktype RowDeleter { input default oftype Sheet; output default oftype Sheet; diff --git a/libs/language-server/src/stdlib/builtin-block-types/SheetPicker.jv b/libs/language-server/src/stdlib/builtin-block-types/SheetPicker.jv index e9591c888..79a4e696c 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/SheetPicker.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/SheetPicker.jv @@ -10,7 +10,7 @@ * sheetName: "AgencyNames"; * } */ -builtin blocktype SheetPicker { +publish builtin blocktype SheetPicker { input default oftype Workbook; output default oftype Sheet; diff --git a/libs/language-server/src/stdlib/builtin-block-types/SqliteLoader.jv b/libs/language-server/src/stdlib/builtin-block-types/SqliteLoader.jv index 3a69197e1..de3bacfc1 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/SqliteLoader.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/SqliteLoader.jv @@ -11,7 +11,7 @@ * file: "./cars.db"; * } */ -builtin blocktype SQLiteLoader { +publish builtin blocktype SQLiteLoader { input default oftype Table; output default oftype None; diff --git a/libs/language-server/src/stdlib/builtin-block-types/TableInterpreter.jv b/libs/language-server/src/stdlib/builtin-block-types/TableInterpreter.jv index b2e66012d..866eacecd 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/TableInterpreter.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/TableInterpreter.jv @@ -25,7 +25,7 @@ * ]; * } */ -builtin blocktype TableInterpreter { +publish builtin blocktype TableInterpreter { input default oftype Sheet; output default oftype Table; diff --git a/libs/language-server/src/stdlib/builtin-block-types/TableTransformer.jv b/libs/language-server/src/stdlib/builtin-block-types/TableTransformer.jv index 6f45a933e..5c3406a57 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/TableTransformer.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/TableTransformer.jv @@ -35,7 +35,7 @@ * uses: CelsiusToFahrenheit; * } */ -builtin blocktype TableTransformer { +publish builtin blocktype TableTransformer { input default oftype Table; output default oftype Table; diff --git a/libs/language-server/src/stdlib/builtin-block-types/TextFileInterpreter.jv b/libs/language-server/src/stdlib/builtin-block-types/TextFileInterpreter.jv index 32e1a46c7..2b04556ac 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/TextFileInterpreter.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/TextFileInterpreter.jv @@ -5,7 +5,7 @@ /** * Interprets a `File` as a `TextFile`. */ -builtin blocktype TextFileInterpreter { +publish builtin blocktype TextFileInterpreter { input default oftype File; output default oftype TextFile; diff --git a/libs/language-server/src/stdlib/builtin-block-types/TextLineDeleter.jv b/libs/language-server/src/stdlib/builtin-block-types/TextLineDeleter.jv index 226996221..30309d332 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/TextLineDeleter.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/TextLineDeleter.jv @@ -5,7 +5,7 @@ /** * Deletes individual lines from a `TextFile`. */ -builtin blocktype TextLineDeleter { +publish builtin blocktype TextLineDeleter { input default oftype TextFile; output default oftype TextFile; diff --git a/libs/language-server/src/stdlib/builtin-block-types/TextRangeSelector.jv b/libs/language-server/src/stdlib/builtin-block-types/TextRangeSelector.jv index df0cda4a2..05ca216e5 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/TextRangeSelector.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/TextRangeSelector.jv @@ -5,7 +5,7 @@ /** * Selects a range of lines from a `TextFile`. */ -builtin blocktype TextRangeSelector { +publish builtin blocktype TextRangeSelector { input default oftype TextFile; output default oftype TextFile; diff --git a/libs/language-server/src/stdlib/builtin-block-types/XlsInterpreter.jv b/libs/language-server/src/stdlib/builtin-block-types/XlsInterpreter.jv index 40c3fcfcc..0d2dd4f7a 100644 --- a/libs/language-server/src/stdlib/builtin-block-types/XlsInterpreter.jv +++ b/libs/language-server/src/stdlib/builtin-block-types/XlsInterpreter.jv @@ -8,7 +8,7 @@ * @example Interprets an input file as a XLSX-file and outputs a `Workbook` containing `Sheet`s. * block AgencyXLSXInterpreter oftype XLSXInterpreter { } */ -builtin blocktype XLSXInterpreter { +publish builtin blocktype XLSXInterpreter { input default oftype File; output default oftype Workbook; } \ No newline at end of file diff --git a/libs/language-server/src/stdlib/builtin-constrainttypes/AllowlistConstraint.jv b/libs/language-server/src/stdlib/builtin-constrainttypes/AllowlistConstraint.jv index a6c25b917..167b35333 100644 --- a/libs/language-server/src/stdlib/builtin-constrainttypes/AllowlistConstraint.jv +++ b/libs/language-server/src/stdlib/builtin-constrainttypes/AllowlistConstraint.jv @@ -6,10 +6,10 @@ * Limits the values to a defined a set of allowed values. Only values in the list are valid. * * @example Only allows the common abbreviations for millisecond, second, minute, etc.. -* constraint TimeUnitString oftype AllowlistConstraint { +* publish constraint TimeUnitString oftype AllowlistConstraint { * allowlist: ["ms", "s", "min", "h", "d", "m", "y"]; * } */ -builtin constrainttype AllowlistConstraint on text { +publish builtin constrainttype AllowlistConstraint on text { property allowlist oftype Collection ; } \ No newline at end of file diff --git a/libs/language-server/src/stdlib/builtin-constrainttypes/DenylistConstraint.jv b/libs/language-server/src/stdlib/builtin-constrainttypes/DenylistConstraint.jv index fc554b2f7..4f0311a89 100644 --- a/libs/language-server/src/stdlib/builtin-constrainttypes/DenylistConstraint.jv +++ b/libs/language-server/src/stdlib/builtin-constrainttypes/DenylistConstraint.jv @@ -6,10 +6,10 @@ * Defines a set of forbidden values. All values in the list are considered invalid. * * @example Denies all primary colors. -* constraint NoPrimaryColors oftype DenylistConstraint { +* publish constraint NoPrimaryColors oftype DenylistConstraint { * denylist: ["red", "blue", "yellow"]; * } */ -builtin constrainttype DenylistConstraint on text { +publish builtin constrainttype DenylistConstraint on text { property denylist oftype Collection ; } \ No newline at end of file diff --git a/libs/language-server/src/stdlib/builtin-constrainttypes/LengthConstraint.jv b/libs/language-server/src/stdlib/builtin-constrainttypes/LengthConstraint.jv index a51f69aa4..7fade58eb 100644 --- a/libs/language-server/src/stdlib/builtin-constrainttypes/LengthConstraint.jv +++ b/libs/language-server/src/stdlib/builtin-constrainttypes/LengthConstraint.jv @@ -6,13 +6,13 @@ * Limits the length of a string with an upper and/or lower boundary. * Only values with a length within the given range are valid. * -* @example A text constraint with 0 to 20 characters. -* constraint ShortAnswerConstraint oftype LengthConstraint { +* @example A text publish constraint with 0 to 20 characters. +* publish constraint ShortAnswerConstraint oftype LengthConstraint { * minLength: 0; * maxLength: 20; * } */ -builtin constrainttype LengthConstraint on text { +publish builtin constrainttype LengthConstraint on text { /** * Inclusive minimum of the valid text length. */ diff --git a/libs/language-server/src/stdlib/builtin-constrainttypes/RangeConstraint.jv b/libs/language-server/src/stdlib/builtin-constrainttypes/RangeConstraint.jv index fa74774a4..03d4184f4 100644 --- a/libs/language-server/src/stdlib/builtin-constrainttypes/RangeConstraint.jv +++ b/libs/language-server/src/stdlib/builtin-constrainttypes/RangeConstraint.jv @@ -6,19 +6,19 @@ * Limits the range of a number value with an upper and/or lower boundary which can be inclusive or exclusive. Only values within the given range are considered valid. * * @example A scale between (and including) 1 and 100. -* constraint HundredScale oftype RangeConstraint { +* publish constraint HundredScale oftype RangeConstraint { * lowerBound: 1; * upperBound: 100; * } * * @example A scale for numbers strictly larger than 1 and less or equal to 100. -* constraint HundredScale oftype RangeConstraint { +* publish constraint HundredScale oftype RangeConstraint { * lowerBound: 1; * lowerBoundInclusive: false; * upperBound: 100; * } */ -builtin constrainttype RangeConstraint on decimal { +publish builtin constrainttype RangeConstraint on decimal { /** * Lower bound for the valid value range. * @see lowerBoundInclusive whether inclusive or exclusive. diff --git a/libs/language-server/src/stdlib/builtin-constrainttypes/RegexConstraint.jv b/libs/language-server/src/stdlib/builtin-constrainttypes/RegexConstraint.jv index 17ef42702..a577eeca0 100644 --- a/libs/language-server/src/stdlib/builtin-constrainttypes/RegexConstraint.jv +++ b/libs/language-server/src/stdlib/builtin-constrainttypes/RegexConstraint.jv @@ -7,10 +7,10 @@ * Only values that comply with the regex are considered valid. * * @example Text that complies with the IPv4 address format. -* constraint IPv4Format oftype RegexConstraint { +* publish constraint IPv4Format oftype RegexConstraint { * regex: /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/; * } */ -builtin constrainttype RegexConstraint on text { +publish builtin constrainttype RegexConstraint on text { property regex oftype Regex ; } \ No newline at end of file diff --git a/libs/language-server/src/stdlib/domain/mobility/GTFSAgencyInterpreter.jv b/libs/language-server/src/stdlib/domain/mobility/GTFSAgencyInterpreter.jv index 61edadc7e..afafbe0b3 100644 --- a/libs/language-server/src/stdlib/domain/mobility/GTFSAgencyInterpreter.jv +++ b/libs/language-server/src/stdlib/domain/mobility/GTFSAgencyInterpreter.jv @@ -6,7 +6,7 @@ * A GTFSAgencyInterpreter interprets a agency.txt file from an extracted ZIP file according to the GTFS standard * See https://gtfs.org/schedule/reference/#agencytxt */ -composite blocktype GTFSAgencyInterpreter { +publish composite blocktype GTFSAgencyInterpreter { input inputPort oftype FileSystem; output outputPort oftype Table; diff --git a/libs/language-server/src/stdlib/domain/mobility/GTFSCalendarDatesInterpreter.jv b/libs/language-server/src/stdlib/domain/mobility/GTFSCalendarDatesInterpreter.jv index b08b0f34f..2e25dcb8d 100644 --- a/libs/language-server/src/stdlib/domain/mobility/GTFSCalendarDatesInterpreter.jv +++ b/libs/language-server/src/stdlib/domain/mobility/GTFSCalendarDatesInterpreter.jv @@ -6,7 +6,7 @@ * A GTFSCalendarDatesInterpreter interprets a calendar_dates.txt file from an extracted ZIP file according to the GTFS standard * See https://gtfs.org/schedule/reference/#calendar_datestxt */ -composite blocktype GTFSCalendarDatesInterpreter { +publish composite blocktype GTFSCalendarDatesInterpreter { input inputPort oftype FileSystem; output outputPort oftype Table; diff --git a/libs/language-server/src/stdlib/domain/mobility/GTFSCalendarInterpreter.jv b/libs/language-server/src/stdlib/domain/mobility/GTFSCalendarInterpreter.jv index be8b32a7c..a6176a102 100644 --- a/libs/language-server/src/stdlib/domain/mobility/GTFSCalendarInterpreter.jv +++ b/libs/language-server/src/stdlib/domain/mobility/GTFSCalendarInterpreter.jv @@ -6,7 +6,7 @@ * A GTFSCalendarInterpreter interprets a calendar.txt file from an extracted ZIP file according to the GTFS standard * See https://gtfs.org/schedule/reference/#calendartxt */ -composite blocktype GTFSCalendarInterpreter { +publish composite blocktype GTFSCalendarInterpreter { input inputPort oftype FileSystem; output outputPort oftype Table; diff --git a/libs/language-server/src/stdlib/domain/mobility/GTFSExtractor.jv b/libs/language-server/src/stdlib/domain/mobility/GTFSExtractor.jv index d5699c36c..7dd76bd36 100644 --- a/libs/language-server/src/stdlib/domain/mobility/GTFSExtractor.jv +++ b/libs/language-server/src/stdlib/domain/mobility/GTFSExtractor.jv @@ -5,7 +5,7 @@ /** * A GTFSExtractor extracts a file from a given URL, interprets it as ZIP and extracts it */ -composite blocktype GTFSExtractor { +publish composite blocktype GTFSExtractor { property url oftype text; input inputPort oftype None; diff --git a/libs/language-server/src/stdlib/domain/mobility/GTFSFareAttributesInterpreter.jv b/libs/language-server/src/stdlib/domain/mobility/GTFSFareAttributesInterpreter.jv index 40c2cb25d..abfba17f9 100644 --- a/libs/language-server/src/stdlib/domain/mobility/GTFSFareAttributesInterpreter.jv +++ b/libs/language-server/src/stdlib/domain/mobility/GTFSFareAttributesInterpreter.jv @@ -6,7 +6,7 @@ * A GTFSFareAttributesInterpreter interprets a fare_attributes.txt file from an extracted ZIP file according to the GTFS standard * See https://gtfs.org/schedule/reference/#fare_attributestxt */ -composite blocktype GTFSFareAttributesInterpreter { +publish composite blocktype GTFSFareAttributesInterpreter { input inputPort oftype FileSystem; output outputPort oftype Table; diff --git a/libs/language-server/src/stdlib/domain/mobility/GTFSFareRulesInterpreter.jv b/libs/language-server/src/stdlib/domain/mobility/GTFSFareRulesInterpreter.jv index 0cd5f9958..ce42b3d5f 100644 --- a/libs/language-server/src/stdlib/domain/mobility/GTFSFareRulesInterpreter.jv +++ b/libs/language-server/src/stdlib/domain/mobility/GTFSFareRulesInterpreter.jv @@ -6,7 +6,7 @@ * A GTFSFareRulesInterpreter interprets a fare_rules.txt file from an extracted ZIP file according to the GTFS standard * See https://gtfs.org/schedule/reference/#fare_rulestxt */ -composite blocktype GTFSFareRulesInterpreter { +publish composite blocktype GTFSFareRulesInterpreter { input inputPort oftype FileSystem; output outputPort oftype Table; diff --git a/libs/language-server/src/stdlib/domain/mobility/GTFSFrequenciesInterpreter.jv b/libs/language-server/src/stdlib/domain/mobility/GTFSFrequenciesInterpreter.jv index 5d812090c..a851292bf 100644 --- a/libs/language-server/src/stdlib/domain/mobility/GTFSFrequenciesInterpreter.jv +++ b/libs/language-server/src/stdlib/domain/mobility/GTFSFrequenciesInterpreter.jv @@ -6,7 +6,7 @@ * A GTFSFrequenciesInterpreter interprets a frequencies.txt file from an extracted ZIP file according to the GTFS standard * See https://gtfs.org/schedule/reference/#frequenciestxt */ -composite blocktype GTFSFrequenciesInterpreter { +publish composite blocktype GTFSFrequenciesInterpreter { input inputPort oftype FileSystem; output outputPort oftype Table; diff --git a/libs/language-server/src/stdlib/domain/mobility/GTFSRoutesInterpreter.jv b/libs/language-server/src/stdlib/domain/mobility/GTFSRoutesInterpreter.jv index 45a8e9ca5..36cc6227e 100644 --- a/libs/language-server/src/stdlib/domain/mobility/GTFSRoutesInterpreter.jv +++ b/libs/language-server/src/stdlib/domain/mobility/GTFSRoutesInterpreter.jv @@ -6,7 +6,7 @@ * A GTFSRoutesInterpreter interprets a routes.txt file from an extracted ZIP file according to the GTFS standard * See https://gtfs.org/schedule/reference/#routestxt */ -composite blocktype GTFSRoutesInterpreter { +publish composite blocktype GTFSRoutesInterpreter { input inputPort oftype FileSystem; output outputPort oftype Table; diff --git a/libs/language-server/src/stdlib/domain/mobility/GTFSShapesInterpreter.jv b/libs/language-server/src/stdlib/domain/mobility/GTFSShapesInterpreter.jv index bf830d44f..de2382829 100644 --- a/libs/language-server/src/stdlib/domain/mobility/GTFSShapesInterpreter.jv +++ b/libs/language-server/src/stdlib/domain/mobility/GTFSShapesInterpreter.jv @@ -6,7 +6,7 @@ * A GTFSShapesInterpreter interprets a shapes.txt file from an extracted ZIP file according to the GTFS standard * See https://gtfs.org/schedule/reference/#shapestxt */ -composite blocktype GTFSShapesInterpreter { +publish composite blocktype GTFSShapesInterpreter { input inputPort oftype FileSystem; output outputPort oftype Table; diff --git a/libs/language-server/src/stdlib/domain/mobility/GTFSStopTimesInterpreter.jv b/libs/language-server/src/stdlib/domain/mobility/GTFSStopTimesInterpreter.jv index 23862c2b7..289e61522 100644 --- a/libs/language-server/src/stdlib/domain/mobility/GTFSStopTimesInterpreter.jv +++ b/libs/language-server/src/stdlib/domain/mobility/GTFSStopTimesInterpreter.jv @@ -6,7 +6,7 @@ * A GTFSStopTimesInterpreter interprets a stop_times.txt file from an extracted ZIP file according to the GTFS standard * See https://gtfs.org/schedule/reference/#stop_timestxt */ -composite blocktype GTFSStopTimesInterpreter { +publish composite blocktype GTFSStopTimesInterpreter { input inputPort oftype FileSystem; output outputPort oftype Table; diff --git a/libs/language-server/src/stdlib/domain/mobility/GTFSStopsInterpreter.jv b/libs/language-server/src/stdlib/domain/mobility/GTFSStopsInterpreter.jv index 3d634491a..9e9ea8323 100644 --- a/libs/language-server/src/stdlib/domain/mobility/GTFSStopsInterpreter.jv +++ b/libs/language-server/src/stdlib/domain/mobility/GTFSStopsInterpreter.jv @@ -6,7 +6,7 @@ * A GTFSStopsInterpreter interprets a stops.txt file from an extracted ZIP file according to the GTFS standard * See https://gtfs.org/schedule/reference/#stopstxt */ -composite blocktype GTFSStopsInterpreter { +publish composite blocktype GTFSStopsInterpreter { input inputPort oftype FileSystem; output outputPort oftype Table; diff --git a/libs/language-server/src/stdlib/domain/mobility/GTFSTripsInterpreter.jv b/libs/language-server/src/stdlib/domain/mobility/GTFSTripsInterpreter.jv index 163e5dcf2..11998662b 100644 --- a/libs/language-server/src/stdlib/domain/mobility/GTFSTripsInterpreter.jv +++ b/libs/language-server/src/stdlib/domain/mobility/GTFSTripsInterpreter.jv @@ -6,7 +6,7 @@ * A GTFSTripsInterpreter interprets a trips.txt file from an extracted ZIP file according to the GTFS standard * See https://gtfs.org/schedule/reference/#tripstxt */ -composite blocktype GTFSTripsInterpreter { +publish composite blocktype GTFSTripsInterpreter { input inputPort oftype FileSystem; output outputPort oftype Table; diff --git a/libs/language-server/src/stdlib/domain/mobility/GTFSValueTypes.jv b/libs/language-server/src/stdlib/domain/mobility/GTFSValueTypes.jv index 3fd76f4db..651afba57 100644 --- a/libs/language-server/src/stdlib/domain/mobility/GTFSValueTypes.jv +++ b/libs/language-server/src/stdlib/domain/mobility/GTFSValueTypes.jv @@ -10,8 +10,8 @@ * Color - A color encoded as a six-digit hexadecimal number. Refer to https://htmlcolorcodes.com to generate a valid value (the leading "#" must not be included). * Example: FFFFFF for white, 000000 for black or 0039A6 for the A,C,E lines in NYMTA. */ -constraint GTFSColorConstraint on text: value matches /^[A-F]{6}$/; -valuetype GTFSColor oftype text { +publish constraint GTFSColorConstraint on text: value matches /^[A-F]{6}$/; +publish valuetype GTFSColor oftype text { constraints: [GTFSColorConstraint]; } @@ -19,8 +19,8 @@ valuetype GTFSColor oftype text { * Date - Service day in the YYYYMMDD format. Since time within a service day may be above 24:00:00, a service day may contain information for the subsequent day(s). * Example: 20180913 for September 13th, 2018. */ -constraint DateYYYYMMDD on text: value matches /^[0-9]{4}[0-9]{2}[0-9]{2}$/; -valuetype GTFSDate oftype text { +publish constraint DateYYYYMMDD on text: value matches /^[0-9]{4}[0-9]{2}[0-9]{2}$/; +publish valuetype GTFSDate oftype text { constraints: [DateYYYYMMDD]; } @@ -28,8 +28,8 @@ valuetype GTFSDate oftype text { * Currency code - An ISO 4217 alphabetical currency code. For the list of current currency, refer to https://en.wikipedia.org/wiki/ISO_4217#Active_codes. * Example: CAD for Canadian dollars, EUR for euros or JPY for Japanese yen. */ -constraint CurrencyConstraint on text: value matches /^[A-Z]{3}$/; -valuetype GTFSCurrency oftype text { +publish constraint CurrencyConstraint on text: value matches /^[A-Z]{3}$/; +publish valuetype GTFSCurrency oftype text { constraints: [CurrencyConstraint]; } @@ -37,25 +37,25 @@ valuetype GTFSCurrency oftype text { * Time - Time in the HH:MM:SS format (H:MM:SS is also accepted). The time is measured from "noon minus 12h" of the service day (effectively midnight except for days on which daylight savings time changes occur). For times occurring after midnight on the service day, enter the time as a value greater than 24:00:00 in HH:MM:SS. * Example: 14:30:00 for 2:30PM or 25:35:00 for 1:35AM on the next day. */ -constraint TimeHHMMSS on text: value matches /^[0-9]{1,2}:{1}[0-9]{2}:{1}[0-9]{2}$/; -valuetype GTFSTime oftype text { +publish constraint TimeHHMMSS on text: value matches /^[0-9]{1,2}:{1}[0-9]{2}:{1}[0-9]{2}$/; +publish valuetype GTFSTime oftype text { constraints: [TimeHHMMSS]; } // Placeholders -constraint EnumTwo on integer: value in [0, 1]; -constraint EnumOneOrTwo on integer: value in [1, 2]; -constraint EnumThree on integer: value in [0, 1, 2]; +publish constraint EnumTwo on integer: value in [0, 1]; +publish constraint EnumOneOrTwo on integer: value in [1, 2]; +publish constraint EnumThree on integer: value in [0, 1, 2]; -valuetype GTFSEnumTwo oftype integer { +publish valuetype GTFSEnumTwo oftype integer { constraints: [EnumTwo]; } -valuetype GTFSEnumOneOrTwo oftype integer { +publish valuetype GTFSEnumOneOrTwo oftype integer { constraints: [EnumOneOrTwo]; } -valuetype GTFSEnumThree oftype integer { +publish valuetype GTFSEnumThree oftype integer { constraints: [EnumThree]; } @@ -65,8 +65,8 @@ valuetype GTFSEnumThree oftype integer { * Latitude - WGS84 latitude in decimal degrees. The value must be greater than or equal to -90.0 and less than or equal to 90.0. * Example: 41.890169 for the Colosseum in Rome. */ -constraint Latitude on decimal: value >= -90 and value <= 90; -valuetype GTFSLatitude oftype decimal { +publish constraint Latitude on decimal: value >= -90 and value <= 90; +publish valuetype GTFSLatitude oftype decimal { constraints: [Latitude]; } @@ -74,24 +74,24 @@ valuetype GTFSLatitude oftype decimal { * Longitude - WGS84 longitude in decimal degrees. The value must be greater than or equal to -180.0 and less than or equal to 180.0. * Example: 12.492269 for the Colosseum in Rome. */ -constraint Longitude on decimal: value >= -180 and value <= 180; -valuetype GTFSLongitude oftype decimal { +publish constraint Longitude on decimal: value >= -180 and value <= 180; +publish valuetype GTFSLongitude oftype decimal { constraints: [Longitude]; } -constraint NonNegativeNumber on decimal: value >= 0; -valuetype GTFSNonNegativeDecimal oftype decimal { +publish constraint NonNegativeNumber on decimal: value >= 0; +publish valuetype GTFSNonNegativeDecimal oftype decimal { constraints: [NonNegativeNumber]; } -valuetype GTFSNonNegativeInteger oftype integer { +publish valuetype GTFSNonNegativeInteger oftype integer { constraints: [NonNegativeNumber]; } /* * URL - A fully qualified URL that includes http:// or https://, and any special characters in the URL must be correctly escaped. See the following http://www.w3.org/Addressing/URL/4_URI_Recommentations.html for a description of how to create fully qualified URL values. */ -constraint URLIncludingSchema on text: value matches /^(http)s?(:\/\/)/; -valuetype GTFSUrl oftype text { +publish constraint URLIncludingSchema on text: value matches /^(http)s?(:\/\/)/; +publish valuetype GTFSUrl oftype text { constraints: [URLIncludingSchema]; } diff --git a/libs/language-server/src/stdlib/percent.jv b/libs/language-server/src/stdlib/percent.jv index a4ab856ee..864e9de1c 100644 --- a/libs/language-server/src/stdlib/percent.jv +++ b/libs/language-server/src/stdlib/percent.jv @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -valuetype Percent oftype decimal { +publish valuetype Percent oftype decimal { constraints: [ZeroToHundredDecimal]; } -constraint ZeroToHundredDecimal on decimal: value >= 0 and value <= 100; \ No newline at end of file +publish constraint ZeroToHundredDecimal on decimal: value >= 0 and value <= 100; \ No newline at end of file diff --git a/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-1.jv b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-1.jv new file mode 100644 index 000000000..576444e6e --- /dev/null +++ b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-1.jv @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use * from './invalid-dependency-cycle-2.jv'; + +publish composite blocktype DependencyCycleBlockType1 { + input inPort oftype None; + output outPort oftype Sheet; + + block TestBlock oftype DependencyCycleBlockType2 {} + + inPort -> TestBlock -> outPort; +} diff --git a/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-2.jv b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-2.jv new file mode 100644 index 000000000..723993d5f --- /dev/null +++ b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-2.jv @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use * from './invalid-dependency-cycle-1.jv'; + +publish composite blocktype DependencyCycleBlockType2 { + input inPort oftype None; + output outPort oftype Sheet; + + block TestBlock oftype DependencyCycleBlockType1 {} + + inPort -> TestBlock -> outPort; +} diff --git a/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-deeper-1.jv b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-deeper-1.jv new file mode 100644 index 000000000..348acee86 --- /dev/null +++ b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-deeper-1.jv @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use * from './invalid-dependency-cycle-deeper-2.jv'; // imported file has import cycle diff --git a/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-deeper-2.jv b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-deeper-2.jv new file mode 100644 index 000000000..542069f7e --- /dev/null +++ b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-deeper-2.jv @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use * from './invalid-dependency-cycle-deeper-3.jv'; + +publish composite blocktype DependencyCycleBlockType2 { + input inPort oftype None; + output outPort oftype Sheet; + + block TestBlock oftype DependencyCycleBlockType1 {} + + inPort -> TestBlock -> outPort; +} diff --git a/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-deeper-3.jv b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-deeper-3.jv new file mode 100644 index 000000000..0d9ce6566 --- /dev/null +++ b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-deeper-3.jv @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use * from './invalid-dependency-cycle-deeper-2.jv'; + +publish composite blocktype DependencyCycleBlockType2 { + input inPort oftype None; + output outPort oftype Sheet; + + block TestBlock oftype DependencyCycleBlockType1 {} + + inPort -> TestBlock -> outPort; +} diff --git a/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-self-import.jv b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-self-import.jv new file mode 100644 index 000000000..21dc8214d --- /dev/null +++ b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-self-import.jv @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use * from './invalid-dependency-cycle-self-import.jv'; // imported file has import cycle diff --git a/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-transitive-1.jv b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-transitive-1.jv new file mode 100644 index 000000000..ee8eaef09 --- /dev/null +++ b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-transitive-1.jv @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use * from './invalid-dependency-cycle-transitive-2.jv'; // imported file has import cycle + +publish composite blocktype DependencyCycleBlockType2 { + input inPort oftype None; + output outPort oftype Sheet; + + block TestBlock oftype DependencyCycleBlockType1 {} + + inPort -> TestBlock -> outPort; +} diff --git a/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-transitive-2.jv b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-transitive-2.jv new file mode 100644 index 000000000..b9b276d1e --- /dev/null +++ b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-transitive-2.jv @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use * from './invalid-dependency-cycle-transitive-3.jv'; + +publish DependencyCycleBlockType2; diff --git a/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-transitive-3.jv b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-transitive-3.jv new file mode 100644 index 000000000..379f3266b --- /dev/null +++ b/libs/language-server/src/test/assets/import-definition/invalid-dependency-cycle-transitive-3.jv @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use * from './invalid-dependency-cycle-transitive-1.jv'; + +publish composite blocktype DependencyCycleBlockType2 { + input inPort oftype None; + output outPort oftype Sheet; + + block TestBlock oftype DependencyCycleBlockType1 {} + + inPort -> TestBlock -> outPort; +} diff --git a/libs/language-server/src/test/assets/import-published-references/published-via-element-definition/invalid-reference-to-element-transitive-no-republish.jv b/libs/language-server/src/test/assets/import-published-references/published-via-element-definition/invalid-reference-to-element-transitive-no-republish.jv new file mode 100644 index 000000000..f7a65e214 --- /dev/null +++ b/libs/language-server/src/test/assets/import-published-references/published-via-element-definition/invalid-reference-to-element-transitive-no-republish.jv @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use * from './not-publishing-transitive.jv'; + +pipeline TestPipeline { + block UsingBlock oftype PublishedBlockType {} +} diff --git a/libs/language-server/src/test/assets/import-published-references/invalid-reference-to-not-existing-element.jv b/libs/language-server/src/test/assets/import-published-references/published-via-element-definition/invalid-reference-to-not-existing-element.jv similarity index 100% rename from libs/language-server/src/test/assets/import-published-references/invalid-reference-to-not-existing-element.jv rename to libs/language-server/src/test/assets/import-published-references/published-via-element-definition/invalid-reference-to-not-existing-element.jv diff --git a/libs/language-server/src/test/assets/import-published-references/invalid-reference-to-not-imported-element.jv b/libs/language-server/src/test/assets/import-published-references/published-via-element-definition/invalid-reference-to-not-imported-element.jv similarity index 100% rename from libs/language-server/src/test/assets/import-published-references/invalid-reference-to-not-imported-element.jv rename to libs/language-server/src/test/assets/import-published-references/published-via-element-definition/invalid-reference-to-not-imported-element.jv diff --git a/libs/language-server/src/test/assets/import-published-references/published-via-element-definition/not-publishing-transitive.jv b/libs/language-server/src/test/assets/import-published-references/published-via-element-definition/not-publishing-transitive.jv new file mode 100644 index 000000000..f8308c3be --- /dev/null +++ b/libs/language-server/src/test/assets/import-published-references/published-via-element-definition/not-publishing-transitive.jv @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use * from './publishing.jv'; + +// automatically, no elements are re-published diff --git a/libs/language-server/src/test/assets/import-published-references/publishing-transitive.jv b/libs/language-server/src/test/assets/import-published-references/published-via-element-definition/publishing-transitive.jv similarity index 78% rename from libs/language-server/src/test/assets/import-published-references/publishing-transitive.jv rename to libs/language-server/src/test/assets/import-published-references/published-via-element-definition/publishing-transitive.jv index 8fb024e25..b951b5202 100644 --- a/libs/language-server/src/test/assets/import-published-references/publishing-transitive.jv +++ b/libs/language-server/src/test/assets/import-published-references/published-via-element-definition/publishing-transitive.jv @@ -3,4 +3,5 @@ // SPDX-License-Identifier: AGPL-3.0-only use * from './publishing.jv'; -// automatically re-publishes all elements + +publish PublishedBlockType; // re-publish diff --git a/libs/language-server/src/test/assets/import-published-references/published-via-element-definition/publishing.jv b/libs/language-server/src/test/assets/import-published-references/published-via-element-definition/publishing.jv new file mode 100644 index 000000000..884de6157 --- /dev/null +++ b/libs/language-server/src/test/assets/import-published-references/published-via-element-definition/publishing.jv @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +publish builtin blocktype PublishedBlockType { + input inPort oftype None; + output outPort oftype None; +} diff --git a/libs/language-server/src/test/assets/import-published-references/valid-reference-to-element-transitive.jv b/libs/language-server/src/test/assets/import-published-references/published-via-element-definition/valid-reference-to-element-transitive.jv similarity index 100% rename from libs/language-server/src/test/assets/import-published-references/valid-reference-to-element-transitive.jv rename to libs/language-server/src/test/assets/import-published-references/published-via-element-definition/valid-reference-to-element-transitive.jv diff --git a/libs/language-server/src/test/assets/import-published-references/valid-reference-to-imported-element.jv b/libs/language-server/src/test/assets/import-published-references/published-via-element-definition/valid-reference-to-imported-element.jv similarity index 100% rename from libs/language-server/src/test/assets/import-published-references/valid-reference-to-imported-element.jv rename to libs/language-server/src/test/assets/import-published-references/published-via-element-definition/valid-reference-to-imported-element.jv diff --git a/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/invalid-reference-to-element-transitive-no-republish.jv b/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/invalid-reference-to-element-transitive-no-republish.jv new file mode 100644 index 000000000..f7a65e214 --- /dev/null +++ b/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/invalid-reference-to-element-transitive-no-republish.jv @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use * from './not-publishing-transitive.jv'; + +pipeline TestPipeline { + block UsingBlock oftype PublishedBlockType {} +} diff --git a/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/invalid-reference-to-not-existing-element.jv b/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/invalid-reference-to-not-existing-element.jv new file mode 100644 index 000000000..5dce66109 --- /dev/null +++ b/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/invalid-reference-to-not-existing-element.jv @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use * from './publishing.jv'; + +pipeline TestPipeline { + block UsingBlock oftype NotPublishedBlockType {} +} diff --git a/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/invalid-reference-to-not-imported-element.jv b/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/invalid-reference-to-not-imported-element.jv new file mode 100644 index 000000000..eccfb294e --- /dev/null +++ b/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/invalid-reference-to-not-imported-element.jv @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline TestPipeline { + block UsingBlock oftype PublishedBlockType {} // ref to not imported element +} diff --git a/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/not-publishing-transitive.jv b/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/not-publishing-transitive.jv new file mode 100644 index 000000000..f8308c3be --- /dev/null +++ b/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/not-publishing-transitive.jv @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use * from './publishing.jv'; + +// automatically, no elements are re-published diff --git a/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/publishing-transitive.jv b/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/publishing-transitive.jv new file mode 100644 index 000000000..b951b5202 --- /dev/null +++ b/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/publishing-transitive.jv @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use * from './publishing.jv'; + +publish PublishedBlockType; // re-publish diff --git a/libs/language-server/src/test/assets/import-published-references/publishing.jv b/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/publishing.jv similarity index 88% rename from libs/language-server/src/test/assets/import-published-references/publishing.jv rename to libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/publishing.jv index 8230e38db..8bd5a7f4b 100644 --- a/libs/language-server/src/test/assets/import-published-references/publishing.jv +++ b/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/publishing.jv @@ -6,3 +6,5 @@ builtin blocktype PublishedBlockType { input inPort oftype None; output outPort oftype None; } + +publish PublishedBlockType; diff --git a/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/valid-reference-to-element-transitive.jv b/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/valid-reference-to-element-transitive.jv new file mode 100644 index 000000000..ac0bb36e8 --- /dev/null +++ b/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/valid-reference-to-element-transitive.jv @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use * from './publishing-transitive.jv'; + +pipeline TestPipeline { + block UsingBlock oftype PublishedBlockType {} +} diff --git a/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/valid-reference-to-imported-element.jv b/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/valid-reference-to-imported-element.jv new file mode 100644 index 000000000..428366c4d --- /dev/null +++ b/libs/language-server/src/test/assets/import-published-references/published-via-publish-definition/valid-reference-to-imported-element.jv @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +use * from './publishing.jv'; + +pipeline TestPipeline { + block UsingBlock oftype PublishedBlockType {} +} diff --git a/libs/language-server/src/test/ast-utils.ts b/libs/language-server/src/test/ast-utils.ts new file mode 100644 index 000000000..621f697c3 --- /dev/null +++ b/libs/language-server/src/test/ast-utils.ts @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { type AstNode, AstUtils, type LangiumDocument } from 'langium'; + +import { isJayveeModel } from '../lib/ast/generated/ast'; + +/** + * Extract all elements that comply with the given filter function. + * Assures that the document contains a JayveeModel and that there is at least one extracted element. + */ +export function extractTestElements( + document: LangiumDocument, + filterFn: (node: AstNode) => node is E, +): E[] { + const model = document.parseResult.value; + expect(isJayveeModel(model)).toBe(true); + + const allElements = AstUtils.streamAllContents(model); + const allSelected = [...allElements.filter(filterFn)]; + expect(allSelected.length).toBeGreaterThan(0); + return allSelected; +} diff --git a/libs/language-server/src/test/index.ts b/libs/language-server/src/test/index.ts index fd181e695..adb4e2b86 100644 --- a/libs/language-server/src/test/index.ts +++ b/libs/language-server/src/test/index.ts @@ -4,3 +4,4 @@ export * from './langium-utils'; export * from './utils'; +export * from './ast-utils';