diff --git a/apps/docs/docs/user/.gitignore b/apps/docs/docs/user/.gitignore new file mode 100644 index 000000000..d1b2321da --- /dev/null +++ b/apps/docs/docs/user/.gitignore @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +# +# SPDX-License-Identifier: AGPL-3.0-only diff --git a/apps/docs/docs/user/constraint-types/_category_.json b/apps/docs/docs/user/constraint-types/_category_.json index 9cced194c..7eecb5c68 100644 --- a/apps/docs/docs/user/constraint-types/_category_.json +++ b/apps/docs/docs/user/constraint-types/_category_.json @@ -1,6 +1,6 @@ { "label": "Constraint Types", - "position": 4, + "position": 5, "link": { "type": "generated-index", "description": "These constraints are shipped with Jayvee and are available right out of the box." diff --git a/apps/docs/docs/user/core-concepts.md b/apps/docs/docs/user/core-concepts.md index 69b0486a2..d46c48ac3 100644 --- a/apps/docs/docs/user/core-concepts.md +++ b/apps/docs/docs/user/core-concepts.md @@ -81,56 +81,29 @@ In the example above, the `url` property of type `text` is defined by the corres A `ValueType` is the definition of a data type of the processed data. Some `Blocks` use `ValueTypes` to define logic (like filtering or assessing the data type in a data sink). We differentiate the following types of `ValueTypes`: -- `Built-in ValueTypes` come with the basic version of Jayvee. - Currently `text`, `decimal`, `integer`, and `boolean` are supported. +- `Built-in ValueTypes` come with the basic version of Jayvee. See [Built-in Valuetypes](./valuetypes/builtin-valuetypes). - `Primitive ValueTypes` can be defined by the user to model domain-specific data types and represent a single value. - `Constraints` can be added to a `Primitive ValueType` (see [below](#constraints)). + `Constraints` can be added to a `Primitive ValueType`. +See [Primitive Valuetypes](./valuetypes/primitive-valuetypes). - `Compound ValueTypes`: UPCOMING. -### Constraints - -`Constraints` for `ValueTypes` declare the validity criteria that each concrete value is checked against. - -#### Syntax 1: Expression syntax - -The syntax of expression-based `Constraints` uses an expression that evaluates to `true` or `false` for the given `value`. The type of the values the expression is working in is indicated ofter the keyword `on`: - ```jayvee +valuetype GasFillLevel oftype integer { + constraints: [ GasFillLevelRange ]; +} + constraint GasFillLevelRange on decimal: value >= 0 and value <= 100; ``` -Refer to the [Expression documentation](./expressions.md) for further reading on expressions. - - -#### Syntax 2: Block-like syntax - -The syntax of `Constraints` is similar to the syntax of `Blocks`. -The availability of property keys and their respective `ValueTypes` is determined by the type of the `Constraint` - indicated by the identifier after the keyword `oftype`: +## Transforms +`Transforms` are used to transform data from one `ValueType` to a different one. For more details, see [Transforms](./transforms.md). ```jayvee -constraint GasFillLevelRange oftype RangeConstraint { - lowerBound: 0; - lowerBoundInclusive: true; - upperBound: 100; - upperBoundInclusive: true; -} -``` - -Note that the type of `Constraint` also determines its applicability to `ValueTypes`. -For instance, a `RangeConstraint` can only be applied to the numerical types `integer` and `decimal`. - -### Primitive ValueTypes - -`Primitive ValueTypes` are based on `Built-in ValueTypes` and use a collection of constraints to restrict the range of valid values. -Such constraints are implicitly connected via a logical `AND` relation. -Note that the `Constraints` need to be applicable to the base-type of the `ValueType` - indicated by the identifier after the keyword `oftype`: +transform CelsiusToKelvin { + from tempCelsius oftype decimal; + to tempKelvin oftype decimal; -```jayvee -valuetype GasFillLevel oftype integer { - constraints: [ GasFillLevelRange ]; + tempKelvin: tempCelsius + 273.15; } -``` - -### Transforms -`Transforms` are used to transform data from one `ValueType` to a different one. For more details, see [Transforms](./transforms.md) \ No newline at end of file +``` \ No newline at end of file diff --git a/apps/docs/docs/user/expressions.md b/apps/docs/docs/user/expressions.md index 57f13573b..c3728c886 100644 --- a/apps/docs/docs/user/expressions.md +++ b/apps/docs/docs/user/expressions.md @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 6 --- # Expressions @@ -9,7 +9,7 @@ Expressions in Jayvee are arbitrarily nested statements. They consist of: - variables (e.g., declared by `from` properties in [Transforms](./transforms.md)) - operators (e.g., `*` or `sqrt`) -Expressions get evaluated at runtime by the interpreter to a [Built-in ValueType](./core-concepts.md#valuetypes). +Expressions get evaluated at runtime by the interpreter to a [Built-in ValueType](./valuetypes/builtin-valuetypes). ### Example diff --git a/apps/docs/docs/user/runtime-parameters.md b/apps/docs/docs/user/runtime-parameters.md index ac5d8995e..4f257fac9 100644 --- a/apps/docs/docs/user/runtime-parameters.md +++ b/apps/docs/docs/user/runtime-parameters.md @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 8 --- # Runtime Parameters diff --git a/apps/docs/docs/user/transforms.md b/apps/docs/docs/user/transforms.md index f232f6dbd..731ec304a 100644 --- a/apps/docs/docs/user/transforms.md +++ b/apps/docs/docs/user/transforms.md @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 7 --- # Transforms diff --git a/apps/docs/docs/user/valuetypes/.gitignore b/apps/docs/docs/user/valuetypes/.gitignore new file mode 100644 index 000000000..eb7531ecd --- /dev/null +++ b/apps/docs/docs/user/valuetypes/.gitignore @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +# +# SPDX-License-Identifier: AGPL-3.0-only + +builtin-valuetypes.md \ No newline at end of file diff --git a/apps/docs/docs/user/valuetypes/_category_.json b/apps/docs/docs/user/valuetypes/_category_.json new file mode 100644 index 000000000..d69f22af2 --- /dev/null +++ b/apps/docs/docs/user/valuetypes/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Valuetypes", + "position": 4, + "link": { + "type": "generated-index", + "description": "Jayvee supports these different kinds of valuetypes." + } +} diff --git a/apps/docs/docs/user/valuetypes/_category_.json.license b/apps/docs/docs/user/valuetypes/_category_.json.license new file mode 100644 index 000000000..17c5d2bad --- /dev/null +++ b/apps/docs/docs/user/valuetypes/_category_.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg + +SPDX-License-Identifier: AGPL-3.0-only diff --git a/apps/docs/docs/user/valuetypes/primitive-valuetypes.md b/apps/docs/docs/user/valuetypes/primitive-valuetypes.md new file mode 100644 index 000000000..92400a538 --- /dev/null +++ b/apps/docs/docs/user/valuetypes/primitive-valuetypes.md @@ -0,0 +1,48 @@ +--- +sidebar_position: 2 +--- +# Primitive ValueTypes + +`Primitive ValueTypes` are based on `Built-in ValueTypes` and use a collection of constraints to restrict the range of valid values. +Such constraints are implicitly connected via a logical `AND` relation. +Note that the `Constraints` need to be applicable to the base-type of the `ValueType` - indicated by the identifier after the keyword `oftype`: + +```jayvee +valuetype GasFillLevel oftype integer { + constraints: [ GasFillLevelRange ]; +} +``` + + +## Constraints + +`Constraints` for `ValueTypes` declare the validity criteria that each concrete value is checked against. + +### Syntax 1: Expression syntax + +The syntax of expression-based `Constraints` uses an expression that evaluates to `true` or `false` for the given `value`. The type of the values the expression is working in is indicated ofter the keyword `on`: + +```jayvee +constraint GasFillLevelRange on decimal: + value >= 0 and value <= 100; +``` + +Refer to the [Expression documentation](../expressions.md) for further reading on expressions. + + +### Syntax 2: Block-like syntax + +The syntax of `Constraints` is similar to the syntax of `Blocks`. +The availability of property keys and their respective `ValueTypes` is determined by the type of the `Constraint` - indicated by the identifier after the keyword `oftype`: + +```jayvee +constraint GasFillLevelRange oftype RangeConstraint { + lowerBound: 0; + lowerBoundInclusive: true; + upperBound: 100; + upperBoundInclusive: true; +} +``` + +Note that the type of `Constraint` also determines its applicability to `ValueTypes`. +For instance, a `RangeConstraint` can only be applied to the numerical types `integer` and `decimal`. \ No newline at end of file diff --git a/apps/docs/docs/user/valuetypes/primitive-valuetypes.md.license b/apps/docs/docs/user/valuetypes/primitive-valuetypes.md.license new file mode 100644 index 000000000..17c5d2bad --- /dev/null +++ b/apps/docs/docs/user/valuetypes/primitive-valuetypes.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg + +SPDX-License-Identifier: AGPL-3.0-only diff --git a/apps/docs/generator/src/main.ts b/apps/docs/generator/src/main.ts index b83837adf..ceeadf3b5 100644 --- a/apps/docs/generator/src/main.ts +++ b/apps/docs/generator/src/main.ts @@ -7,6 +7,7 @@ import { join } from 'path'; import { StdLangExtension } from '@jvalue/jayvee-extensions/std/lang'; import { + PrimitiveValuetypes, getRegisteredBlockMetaInformation, getRegisteredConstraintMetaInformation, registerConstraints, @@ -21,6 +22,7 @@ function main(): void { const rootPath = join(__dirname, '..', '..', '..', '..'); generateBlockTypeDocs(rootPath); generateConstraintTypeDocs(rootPath); + generateValueTypeDocs(rootPath); } function generateBlockTypeDocs(rootPath: string): void { @@ -69,4 +71,17 @@ function generateConstraintTypeDocs(rootPath: string): void { } } +function generateValueTypeDocs(rootPath: string): void { + const docsPath = join(rootPath, 'apps', 'docs', 'docs', 'user', 'valuetypes'); + const userDocBuilder = new UserDocGenerator(); + const valueTypeDoc = + userDocBuilder.generateValueTypesDoc(PrimitiveValuetypes); + + const fileName = `builtin-valuetypes.md`; + writeFileSync(join(docsPath, fileName), valueTypeDoc, { + flag: 'w', + }); + console.info(`Generated file ${fileName}`); +} + main(); diff --git a/apps/docs/generator/src/user-doc-generator.ts b/apps/docs/generator/src/user-doc-generator.ts index 4269abe58..23781d642 100644 --- a/apps/docs/generator/src/user-doc-generator.ts +++ b/apps/docs/generator/src/user-doc-generator.ts @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: AGPL-3.0-only +import { strict as assert } from 'assert'; + import { BlockMetaInformation, ConstraintMetaInformation, @@ -9,13 +11,67 @@ import { IOType, JayveeBlockTypeDocGenerator, JayveeConstraintTypeDocGenerator, + JayveeValueTypesDocGenerator, MarkdownBuilder, + PrimitiveValuetype, PropertySpecification, } from '@jvalue/jayvee-language-server'; export class UserDocGenerator - implements JayveeBlockTypeDocGenerator, JayveeConstraintTypeDocGenerator + implements + JayveeBlockTypeDocGenerator, + JayveeConstraintTypeDocGenerator, + JayveeValueTypesDocGenerator { + generateValueTypesDoc(valueTypes: { + [name: string]: PrimitiveValuetype; + }): string { + const builder = new UserDocMarkdownBuilder() + .docTitle('Built-in Valuetypes') + .generationComment() + .description( + ` +For an introduction to valuetypes, see the [Core Concepts](../core-concepts). +Built-in valuetypes come with the basic version of Jayvee. +They are the basis for more restricted [Primitive Valuetypes](./primitive-valuetypes) +that fullfil [Constraints](./primitive-valuetypes#constraints).`.trim(), + 1, + ) + .heading('Available built-in valuetypes', 1); + + Object.entries(valueTypes) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .filter(([_, valueType]) => valueType.isUserExtendable()) + .forEach(([name, valueType]) => { + assert( + valueType.getUserDoc(), + `Documentation is missing for user extendable value type: ${valueType.getName()}`, + ); + builder + .heading(name, 2) + .description(valueType.getUserDoc() ?? '', 3) + .examples( + [ + { + code: ` +block ExampleTableInterpreter oftype TableInterpreter { + header: true; + columns: [ + "columnName" oftype ${valueType.getName()} + ]; +}`.trim(), + description: `A block of type \`TableInterpreter\` that + interprets data in the column \`columnName\` as \`${valueType.getName()}\`. + `.trim(), + }, + ], + 3, + ); + }); + + return builder.build(); + } + generateBlockTypeDoc(metaInf: BlockMetaInformation): string { const builder = new UserDocMarkdownBuilder() .docTitle(metaInf.type) @@ -84,6 +140,11 @@ class UserDocMarkdownBuilder { return this; } + heading(heading: string, depth = 1): UserDocMarkdownBuilder { + this.markdownBuilder.heading(heading, depth); + return this; + } + propertyHeading(propertyName: string, depth = 1): UserDocMarkdownBuilder { this.markdownBuilder.heading(`\`${propertyName}\``, depth); return this; diff --git a/apps/vs-code-extension/src/standard-library-file-system-provider.ts b/apps/vs-code-extension/src/standard-library-file-system-provider.ts index 91ee7b933..1b9c29b69 100644 --- a/apps/vs-code-extension/src/standard-library-file-system-provider.ts +++ b/apps/vs-code-extension/src/standard-library-file-system-provider.ts @@ -23,6 +23,10 @@ export class StandardLibraryFileSystemProvider implements FileSystemProvider { onDidChangeFile = this.didChangeFile.event; constructor() { + this.registerStdLib(); + } + + private registerStdLib() { Object.entries(StdLib).forEach(([libName, lib]) => { this.libraries.set( Uri.parse(libName).toString(), // removes slashes if missing authorities, required for matching later on diff --git a/libs/extensions/std/lang/src/gtfs-rt-interpreter-meta-inf.spec.ts b/libs/extensions/std/lang/src/gtfs-rt-interpreter-meta-inf.spec.ts new file mode 100644 index 000000000..9110e1635 --- /dev/null +++ b/libs/extensions/std/lang/src/gtfs-rt-interpreter-meta-inf.spec.ts @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { + createJayveeServices, + useExtension, +} from '@jvalue/jayvee-language-server'; +import { + TestLangExtension, + ValidationResult, + readJvTestAssetHelper, + validationHelper, +} from '@jvalue/jayvee-language-server/test'; +import { AstNode } from 'langium'; +import { NodeFileSystem } from 'langium/node'; + +import { StdLangExtension } from './extension'; + +describe('Validation of GtfsRTInterpreterMetaInformation', () => { + let validate: (input: string) => Promise>; + + const readJvTestAsset = readJvTestAssetHelper(__dirname, '../test/assets/'); + + beforeAll(() => { + // Register std extension + useExtension(new StdLangExtension()); + // Register test extension + useExtension(new TestLangExtension()); + // Create language services + const services = createJayveeServices(NodeFileSystem).Jayvee; + // Create validation helper for language services + validate = validationHelper(services); + }); + + it('should diagnose no error on valid entity parameter value', async () => { + const text = readJvTestAsset( + 'gtfs-rt-interpreter-meta-inf/valid-valid-entity-param.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(0); + }); + + it('should diagnose error on invalid entity parameter value', async () => { + const text = readJvTestAsset( + 'gtfs-rt-interpreter-meta-inf/invalid-invalid-entity-param.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(1); + expect(diagnostics).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + message: 'Entity must be "trip_update", "alert" or "vehicle"', + }), + ]), + ); + }); +}); diff --git a/libs/extensions/std/lang/src/text-file-interpreter-meta-inf.spec.ts b/libs/extensions/std/lang/src/text-file-interpreter-meta-inf.spec.ts new file mode 100644 index 000000000..60c7fbd4d --- /dev/null +++ b/libs/extensions/std/lang/src/text-file-interpreter-meta-inf.spec.ts @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { + createJayveeServices, + useExtension, +} from '@jvalue/jayvee-language-server'; +import { + TestLangExtension, + ValidationResult, + readJvTestAssetHelper, + validationHelper, +} from '@jvalue/jayvee-language-server/test'; +import { AstNode } from 'langium'; +import { NodeFileSystem } from 'langium/node'; + +import { StdLangExtension } from './extension'; + +describe('Validation of TextFileInterpreterMetaInformation', () => { + let validate: (input: string) => Promise>; + + const readJvTestAsset = readJvTestAssetHelper(__dirname, '../test/assets/'); + + beforeAll(() => { + // Register std extension + useExtension(new StdLangExtension()); + // Register test extension + useExtension(new TestLangExtension()); + // Create language services + const services = createJayveeServices(NodeFileSystem).Jayvee; + // Create validation helper for language services + validate = validationHelper(services); + }); + + it('should diagnose error on invalid encoding parameter value', async () => { + const text = readJvTestAsset( + 'text-file-interpreter-meta-inf/invalid-invalid-encoding-param.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(1); + expect(diagnostics).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + message: 'Unknown encoding "invalid"', + }), + ]), + ); + }); + + it('should diagnose no error', async () => { + const text = readJvTestAsset( + 'text-file-interpreter-meta-inf/valid-utf8-encoding.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(0); + }); +}); diff --git a/libs/extensions/std/lang/src/text-line-deleter-meta-inf.spec.ts b/libs/extensions/std/lang/src/text-line-deleter-meta-inf.spec.ts new file mode 100644 index 000000000..eeacaf4e2 --- /dev/null +++ b/libs/extensions/std/lang/src/text-line-deleter-meta-inf.spec.ts @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { + createJayveeServices, + useExtension, +} from '@jvalue/jayvee-language-server'; +import { + TestLangExtension, + ValidationResult, + readJvTestAssetHelper, + validationHelper, +} from '@jvalue/jayvee-language-server/test'; +import { AstNode } from 'langium'; +import { NodeFileSystem } from 'langium/node'; + +import { StdLangExtension } from './extension'; + +describe('Validation of TextLineDeleterMetaInformation', () => { + let validate: (input: string) => Promise>; + + const readJvTestAsset = readJvTestAssetHelper(__dirname, '../test/assets/'); + + beforeAll(() => { + // Register std extension + useExtension(new StdLangExtension()); + // Register test extension + useExtension(new TestLangExtension()); + // Create language services + const services = createJayveeServices(NodeFileSystem).Jayvee; + // Create validation helper for language services + validate = validationHelper(services); + }); + + it('should diagnose error on line parameter less or equal to zero', async () => { + const text = readJvTestAsset( + 'text-line-deleter-meta-inf/invalid-line-less-or-equal-zero.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(3); + expect(diagnostics).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + message: 'Line numbers need to be greater than zero', + }), + ]), + ); + }); + + it('should diagnose no error', async () => { + const text = readJvTestAsset( + 'text-line-deleter-meta-inf/valid-postive-line-number.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(0); + }); +}); diff --git a/libs/extensions/std/lang/src/text-range-selector-meta-inf.spec.ts b/libs/extensions/std/lang/src/text-range-selector-meta-inf.spec.ts new file mode 100644 index 000000000..d0fdd645d --- /dev/null +++ b/libs/extensions/std/lang/src/text-range-selector-meta-inf.spec.ts @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { + createJayveeServices, + useExtension, +} from '@jvalue/jayvee-language-server'; +import { + TestLangExtension, + ValidationResult, + readJvTestAssetHelper, + validationHelper, +} from '@jvalue/jayvee-language-server/test'; +import { AstNode } from 'langium'; +import { NodeFileSystem } from 'langium/node'; + +import { StdLangExtension } from './extension'; + +describe('Validation of TextRangeSelectorMetaInformation', () => { + let validate: (input: string) => Promise>; + + const readJvTestAsset = readJvTestAssetHelper(__dirname, '../test/assets/'); + + beforeAll(() => { + // Register std extension + useExtension(new StdLangExtension()); + // Register test extension + useExtension(new TestLangExtension()); + // Create language services + const services = createJayveeServices(NodeFileSystem).Jayvee; + // Create validation helper for language services + validate = validationHelper(services); + }); + + it('should diagnose error on lineFrom parameter less or equal to zero', async () => { + const text = readJvTestAsset( + 'text-range-selector-meta-inf/invalid-lineFrom-less-or-equal-zero.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(1); + expect(diagnostics).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + message: 'Line numbers need to be greater than zero', + }), + ]), + ); + }); + + it('should diagnose error on lineTo parameter less or equal to zero', async () => { + const text = readJvTestAsset( + 'text-range-selector-meta-inf/invalid-lineTo-less-or-equal-zero.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(2); + expect(diagnostics).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + message: 'Line numbers need to be greater than zero', + }), + ]), + ); + }); + + it('should diagnose error on lineFrom > lineTo', async () => { + const text = readJvTestAsset( + 'text-range-selector-meta-inf/invalid-lineFrom-greater-lineTo.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(2); + expect(diagnostics).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + message: + 'The lower line number needs to be smaller or equal to the upper line number', + }), + ]), + ); + }); + + it('should diagnose no error', async () => { + const text = readJvTestAsset( + 'text-range-selector-meta-inf/valid-correct-range.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(0); + }); +}); diff --git a/libs/extensions/std/lang/test/assets/gtfs-rt-interpreter-meta-inf/invalid-invalid-entity-param.jv b/libs/extensions/std/lang/test/assets/gtfs-rt-interpreter-meta-inf/invalid-invalid-entity-param.jv new file mode 100644 index 000000000..abcdd2705 --- /dev/null +++ b/libs/extensions/std/lang/test/assets/gtfs-rt-interpreter-meta-inf/invalid-invalid-entity-param.jv @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype GtfsRTInterpreter { + entity: 'invalid'; + } + + block TestExtractor oftype TestFileExtractor { + } + + block TestLoader oftype TestSheetLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/std/lang/test/assets/gtfs-rt-interpreter-meta-inf/valid-valid-entity-param.jv b/libs/extensions/std/lang/test/assets/gtfs-rt-interpreter-meta-inf/valid-valid-entity-param.jv new file mode 100644 index 000000000..98acd3a98 --- /dev/null +++ b/libs/extensions/std/lang/test/assets/gtfs-rt-interpreter-meta-inf/valid-valid-entity-param.jv @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype GtfsRTInterpreter { + entity: 'alert'; + } + + block TestExtractor oftype TestFileExtractor { + } + + block TestLoader oftype TestSheetLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/std/lang/test/assets/text-file-interpreter-meta-inf/invalid-invalid-encoding-param.jv b/libs/extensions/std/lang/test/assets/text-file-interpreter-meta-inf/invalid-invalid-encoding-param.jv new file mode 100644 index 000000000..4a9dbe87b --- /dev/null +++ b/libs/extensions/std/lang/test/assets/text-file-interpreter-meta-inf/invalid-invalid-encoding-param.jv @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype TextFileInterpreter { + encoding: 'invalid'; + } + + block TestExtractor oftype TestFileExtractor { + } + + block TestLoader oftype TestTextFileLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/std/lang/test/assets/text-file-interpreter-meta-inf/valid-utf8-encoding.jv b/libs/extensions/std/lang/test/assets/text-file-interpreter-meta-inf/valid-utf8-encoding.jv new file mode 100644 index 000000000..7537150f1 --- /dev/null +++ b/libs/extensions/std/lang/test/assets/text-file-interpreter-meta-inf/valid-utf8-encoding.jv @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype TextFileInterpreter { + encoding: 'utf8'; + } + + block TestExtractor oftype TestFileExtractor { + } + + block TestLoader oftype TestTextFileLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/std/lang/test/assets/text-line-deleter-meta-inf/invalid-line-less-or-equal-zero.jv b/libs/extensions/std/lang/test/assets/text-line-deleter-meta-inf/invalid-line-less-or-equal-zero.jv new file mode 100644 index 000000000..68f2df265 --- /dev/null +++ b/libs/extensions/std/lang/test/assets/text-line-deleter-meta-inf/invalid-line-less-or-equal-zero.jv @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype TextLineDeleter { + lines: [2,3,0,-1,-20]; + } + + block TestExtractor oftype TestTextFileExtractor { + } + + block TestLoader oftype TestTextFileLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/std/lang/test/assets/text-line-deleter-meta-inf/valid-postive-line-number.jv b/libs/extensions/std/lang/test/assets/text-line-deleter-meta-inf/valid-postive-line-number.jv new file mode 100644 index 000000000..fc7709419 --- /dev/null +++ b/libs/extensions/std/lang/test/assets/text-line-deleter-meta-inf/valid-postive-line-number.jv @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype TextLineDeleter { + lines: [2,3]; + } + + block TestExtractor oftype TestTextFileExtractor { + } + + block TestLoader oftype TestTextFileLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/std/lang/test/assets/text-range-selector-meta-inf/invalid-lineFrom-greater-lineTo.jv b/libs/extensions/std/lang/test/assets/text-range-selector-meta-inf/invalid-lineFrom-greater-lineTo.jv new file mode 100644 index 000000000..cdd0a588c --- /dev/null +++ b/libs/extensions/std/lang/test/assets/text-range-selector-meta-inf/invalid-lineFrom-greater-lineTo.jv @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype TextRangeSelector { + lineFrom: 10; + lineTo: 1; + } + + block TestExtractor oftype TestTextFileExtractor { + } + + block TestLoader oftype TestTextFileLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/std/lang/test/assets/text-range-selector-meta-inf/invalid-lineFrom-less-or-equal-zero.jv b/libs/extensions/std/lang/test/assets/text-range-selector-meta-inf/invalid-lineFrom-less-or-equal-zero.jv new file mode 100644 index 000000000..6bce31112 --- /dev/null +++ b/libs/extensions/std/lang/test/assets/text-range-selector-meta-inf/invalid-lineFrom-less-or-equal-zero.jv @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype TextRangeSelector { + lineFrom: -1; + } + + block TestExtractor oftype TestTextFileExtractor { + } + + block TestLoader oftype TestTextFileLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/std/lang/test/assets/text-range-selector-meta-inf/invalid-lineTo-less-or-equal-zero.jv b/libs/extensions/std/lang/test/assets/text-range-selector-meta-inf/invalid-lineTo-less-or-equal-zero.jv new file mode 100644 index 000000000..bcb03507e --- /dev/null +++ b/libs/extensions/std/lang/test/assets/text-range-selector-meta-inf/invalid-lineTo-less-or-equal-zero.jv @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype TextRangeSelector { + lineFrom: -2; + lineTo: -1; + } + + block TestExtractor oftype TestTextFileExtractor { + } + + block TestLoader oftype TestTextFileLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/std/lang/test/assets/text-range-selector-meta-inf/valid-correct-range.jv b/libs/extensions/std/lang/test/assets/text-range-selector-meta-inf/valid-correct-range.jv new file mode 100644 index 000000000..3359596c0 --- /dev/null +++ b/libs/extensions/std/lang/test/assets/text-range-selector-meta-inf/valid-correct-range.jv @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype TextRangeSelector { + lineFrom: 1; + lineTo: 2; + } + + block TestExtractor oftype TestTextFileExtractor { + } + + block TestLoader oftype TestTextFileLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/tabular/lang/project.json b/libs/extensions/tabular/lang/project.json index 7d9a0bf4f..287c6e5ce 100644 --- a/libs/extensions/tabular/lang/project.json +++ b/libs/extensions/tabular/lang/project.json @@ -26,7 +26,7 @@ "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], "options": { "jestConfig": "libs/extensions/tabular/lang/jest.config.ts", - "passWithNoTests": true + "passWithNoTests": false } } }, diff --git a/libs/extensions/tabular/lang/src/lib/cell-writer-meta-inf.spec.ts b/libs/extensions/tabular/lang/src/lib/cell-writer-meta-inf.spec.ts new file mode 100644 index 000000000..10413f0b8 --- /dev/null +++ b/libs/extensions/tabular/lang/src/lib/cell-writer-meta-inf.spec.ts @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { + createJayveeServices, + useExtension, +} from '@jvalue/jayvee-language-server'; +import { + TestLangExtension, + ValidationResult, + readJvTestAssetHelper, + validationHelper, +} from '@jvalue/jayvee-language-server/test'; +import { AstNode } from 'langium'; +import { NodeFileSystem } from 'langium/node'; + +import { TabularLangExtension } from '../extension'; + +describe('Validation of CellWriterMetaInformation', () => { + let validate: (input: string) => Promise>; + + const readJvTestAsset = readJvTestAssetHelper( + __dirname, + '../../test/assets/', + ); + + beforeAll(() => { + // Register std extension + useExtension(new TabularLangExtension()); + // Register test extension + useExtension(new TestLangExtension()); + // Create language services + const services = createJayveeServices(NodeFileSystem).Jayvee; + // Create validation helper for language services + validate = validationHelper(services); + }); + + it('should diagnose error on wrong dimension for at parameter', async () => { + const text = readJvTestAsset( + 'cell-writer-meta-inf/invalid-wrong-at-dimension.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(1); + expect(diagnostics).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + message: 'The cell range needs to be one-dimensional', + }), + ]), + ); + }); + + it('should diagnose error on number of write values does not match cell range', async () => { + const text = readJvTestAsset( + 'cell-writer-meta-inf/invalid-write-length-does-not-match-cell-range.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(2); + expect(diagnostics).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + message: + 'The number of values to write (3) does not match the number of cells (4)', + }), + expect.objectContaining({ + message: + 'The number of values to write (3) does not match the number of cells (4)', + }), + ]), + ); + }); + + it('should diagnose no error', async () => { + const text = readJvTestAsset( + 'cell-writer-meta-inf/valid-range-matches-array-length.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(0); + }); +}); diff --git a/libs/extensions/tabular/lang/src/lib/column-deleter-meta-inf.spec.ts b/libs/extensions/tabular/lang/src/lib/column-deleter-meta-inf.spec.ts new file mode 100644 index 000000000..0875860f4 --- /dev/null +++ b/libs/extensions/tabular/lang/src/lib/column-deleter-meta-inf.spec.ts @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { + createJayveeServices, + useExtension, +} from '@jvalue/jayvee-language-server'; +import { + TestLangExtension, + ValidationResult, + readJvTestAssetHelper, + validationHelper, +} from '@jvalue/jayvee-language-server/test'; +import { AstNode } from 'langium'; +import { NodeFileSystem } from 'langium/node'; + +import { TabularLangExtension } from '../extension'; + +describe('Validation of ColumnDeleterMetaInformation', () => { + let validate: (input: string) => Promise>; + + const readJvTestAsset = readJvTestAssetHelper( + __dirname, + '../../test/assets/', + ); + + beforeAll(() => { + // Register std extension + useExtension(new TabularLangExtension()); + // Register test extension + useExtension(new TestLangExtension()); + // Create language services + const services = createJayveeServices(NodeFileSystem).Jayvee; + // Create validation helper for language services + validate = validationHelper(services); + }); + + it('should diagnose error on deleting partial column', async () => { + const text = readJvTestAsset( + 'column-deleter-meta-inf/invalid-partial-column-delete.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(1); + expect(diagnostics).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + message: 'An entire column needs to be selected', + }), + ]), + ); + }); + + it('should diagnose no error', async () => { + const text = readJvTestAsset( + 'column-deleter-meta-inf/valid-column-delete.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(0); + }); +}); diff --git a/libs/extensions/tabular/lang/src/lib/row-deleter-meta-inf.spec.ts b/libs/extensions/tabular/lang/src/lib/row-deleter-meta-inf.spec.ts new file mode 100644 index 000000000..4ec568451 --- /dev/null +++ b/libs/extensions/tabular/lang/src/lib/row-deleter-meta-inf.spec.ts @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { + createJayveeServices, + useExtension, +} from '@jvalue/jayvee-language-server'; +import { + TestLangExtension, + ValidationResult, + readJvTestAssetHelper, + validationHelper, +} from '@jvalue/jayvee-language-server/test'; +import { AstNode } from 'langium'; +import { NodeFileSystem } from 'langium/node'; + +import { TabularLangExtension } from '../extension'; + +describe('Validation of RowDeleterMetaInformation', () => { + let validate: (input: string) => Promise>; + + const readJvTestAsset = readJvTestAssetHelper( + __dirname, + '../../test/assets/', + ); + + beforeAll(() => { + // Register std extension + useExtension(new TabularLangExtension()); + // Register test extension + useExtension(new TestLangExtension()); + // Create language services + const services = createJayveeServices(NodeFileSystem).Jayvee; + // Create validation helper for language services + validate = validationHelper(services); + }); + + it('should diagnose error on deleting partial row', async () => { + const text = readJvTestAsset( + 'row-deleter-meta-inf/invalid-partial-row-delete.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(1); + expect(diagnostics).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + message: 'An entire row needs to be selected', + }), + ]), + ); + }); + + it('should diagnose no error', async () => { + const text = readJvTestAsset('row-deleter-meta-inf/valid-row-delete.jv'); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(0); + }); +}); diff --git a/libs/extensions/tabular/lang/src/lib/table-interpreter-meta-inf.spec.ts b/libs/extensions/tabular/lang/src/lib/table-interpreter-meta-inf.spec.ts new file mode 100644 index 000000000..0ad7f640c --- /dev/null +++ b/libs/extensions/tabular/lang/src/lib/table-interpreter-meta-inf.spec.ts @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { + createJayveeServices, + useExtension, +} from '@jvalue/jayvee-language-server'; +import { + TestLangExtension, + ValidationResult, + readJvTestAssetHelper, + validationHelper, +} from '@jvalue/jayvee-language-server/test'; +import { AstNode } from 'langium'; +import { NodeFileSystem } from 'langium/node'; + +import { TabularLangExtension } from '../extension'; + +describe('Validation of TableInterpreterMetaInformation', () => { + let validate: (input: string) => Promise>; + + const readJvTestAsset = readJvTestAssetHelper( + __dirname, + '../../test/assets/', + ); + + beforeAll(() => { + // Register std extension + useExtension(new TabularLangExtension()); + // Register test extension + useExtension(new TestLangExtension()); + // Create language services + const services = createJayveeServices(NodeFileSystem).Jayvee; + // Create validation helper for language services + validate = validationHelper(services); + }); + + it('should diagnose error on non unique column names', async () => { + const text = readJvTestAsset( + 'table-interpreter-meta-inf/invalid-non-unique-column-names.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(2); + expect(diagnostics).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + message: 'The column name "name" needs to be unique.', + }), + expect.objectContaining({ + message: 'The column name "name" needs to be unique.', + }), + ]), + ); + }); + + it('should diagnose no error', async () => { + const text = readJvTestAsset( + 'table-interpreter-meta-inf/valid-correct-table.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(0); + }); +}); diff --git a/libs/extensions/tabular/lang/src/lib/table-transformer-meta-inf.spec.ts b/libs/extensions/tabular/lang/src/lib/table-transformer-meta-inf.spec.ts new file mode 100644 index 000000000..6f4fff372 --- /dev/null +++ b/libs/extensions/tabular/lang/src/lib/table-transformer-meta-inf.spec.ts @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { + createJayveeServices, + useExtension, +} from '@jvalue/jayvee-language-server'; +import { + TestLangExtension, + ValidationResult, + readJvTestAssetHelper, + validationHelper, +} from '@jvalue/jayvee-language-server/test'; +import { AstNode } from 'langium'; +import { NodeFileSystem } from 'langium/node'; + +import { TabularLangExtension } from '../extension'; + +describe('Validation of TableTransformerMetaInformation', () => { + let validate: (input: string) => Promise>; + + const readJvTestAsset = readJvTestAssetHelper( + __dirname, + '../../test/assets/', + ); + + beforeAll(() => { + // Register std extension + useExtension(new TabularLangExtension()); + // Register test extension + useExtension(new TestLangExtension()); + // Create language services + const services = createJayveeServices(NodeFileSystem).Jayvee; + // Create validation helper for language services + validate = validationHelper(services); + }); + + it('should diagnose error on number of input columns do not match transform input ports', async () => { + const text = readJvTestAsset( + 'table-transformer-meta-inf/invalid-input-columns-transform-port-missmatch.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(1); + expect(diagnostics).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + message: 'Expected 1 columns but only got 2', + }), + ]), + ); + }); + + it('should diagnose no error', async () => { + const text = readJvTestAsset( + 'table-transformer-meta-inf/valid-correct-ports.jv', + ); + + const validationResult = await validate(text); + const diagnostics = validationResult.diagnostics; + + expect(diagnostics).toHaveLength(0); + }); +}); diff --git a/libs/extensions/tabular/lang/test/assets/cell-writer-meta-inf/invalid-write-length-does-not-match-cell-range.jv b/libs/extensions/tabular/lang/test/assets/cell-writer-meta-inf/invalid-write-length-does-not-match-cell-range.jv new file mode 100644 index 000000000..9384c99aa --- /dev/null +++ b/libs/extensions/tabular/lang/test/assets/cell-writer-meta-inf/invalid-write-length-does-not-match-cell-range.jv @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype CellWriter { + at: range A1:A4; + write: ['values', 'to', 'write']; + } + + block TestExtractor oftype TestSheetExtractor { + } + + block TestLoader oftype TestSheetLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/tabular/lang/test/assets/cell-writer-meta-inf/invalid-wrong-at-dimension.jv b/libs/extensions/tabular/lang/test/assets/cell-writer-meta-inf/invalid-wrong-at-dimension.jv new file mode 100644 index 000000000..d8154bf76 --- /dev/null +++ b/libs/extensions/tabular/lang/test/assets/cell-writer-meta-inf/invalid-wrong-at-dimension.jv @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype CellWriter { + at: range A1:B2; + write: ['the', 'values', 'to', 'write']; + } + + block TestExtractor oftype TestSheetExtractor { + } + + block TestLoader oftype TestSheetLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/tabular/lang/test/assets/cell-writer-meta-inf/valid-range-matches-array-length.jv b/libs/extensions/tabular/lang/test/assets/cell-writer-meta-inf/valid-range-matches-array-length.jv new file mode 100644 index 000000000..2d9a51d5f --- /dev/null +++ b/libs/extensions/tabular/lang/test/assets/cell-writer-meta-inf/valid-range-matches-array-length.jv @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype CellWriter { + at: range A1:A3; + write: ['values', 'to', 'write']; + } + + block TestExtractor oftype TestSheetExtractor { + } + + block TestLoader oftype TestSheetLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/tabular/lang/test/assets/column-deleter-meta-inf/invalid-partial-column-delete.jv b/libs/extensions/tabular/lang/test/assets/column-deleter-meta-inf/invalid-partial-column-delete.jv new file mode 100644 index 000000000..981dab3ae --- /dev/null +++ b/libs/extensions/tabular/lang/test/assets/column-deleter-meta-inf/invalid-partial-column-delete.jv @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype ColumnDeleter { + delete: [range A1:A3]; + } + + block TestExtractor oftype TestSheetExtractor { + } + + block TestLoader oftype TestSheetLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/tabular/lang/test/assets/column-deleter-meta-inf/valid-column-delete.jv b/libs/extensions/tabular/lang/test/assets/column-deleter-meta-inf/valid-column-delete.jv new file mode 100644 index 000000000..29da54be1 --- /dev/null +++ b/libs/extensions/tabular/lang/test/assets/column-deleter-meta-inf/valid-column-delete.jv @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype ColumnDeleter { + delete: [column A]; + } + + block TestExtractor oftype TestSheetExtractor { + } + + block TestLoader oftype TestSheetLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/tabular/lang/test/assets/row-deleter-meta-inf/invalid-partial-row-delete.jv b/libs/extensions/tabular/lang/test/assets/row-deleter-meta-inf/invalid-partial-row-delete.jv new file mode 100644 index 000000000..80c2e8e34 --- /dev/null +++ b/libs/extensions/tabular/lang/test/assets/row-deleter-meta-inf/invalid-partial-row-delete.jv @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype RowDeleter { + delete: [range A1:C1]; + } + + block TestExtractor oftype TestSheetExtractor { + } + + block TestLoader oftype TestSheetLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/tabular/lang/test/assets/row-deleter-meta-inf/valid-row-delete.jv b/libs/extensions/tabular/lang/test/assets/row-deleter-meta-inf/valid-row-delete.jv new file mode 100644 index 000000000..673fe5f6d --- /dev/null +++ b/libs/extensions/tabular/lang/test/assets/row-deleter-meta-inf/valid-row-delete.jv @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype RowDeleter { + delete: [row 1]; + } + + block TestExtractor oftype TestSheetExtractor { + } + + block TestLoader oftype TestSheetLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/tabular/lang/test/assets/table-interpreter-meta-inf/invalid-non-unique-column-names.jv b/libs/extensions/tabular/lang/test/assets/table-interpreter-meta-inf/invalid-non-unique-column-names.jv new file mode 100644 index 000000000..197e2d49a --- /dev/null +++ b/libs/extensions/tabular/lang/test/assets/table-interpreter-meta-inf/invalid-non-unique-column-names.jv @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype TableInterpreter { + header: false; + columns: [ + "name" oftype text, + "name" oftype integer, + ]; + } + + block TestExtractor oftype TestSheetExtractor { + } + + block TestLoader oftype TestTableLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/tabular/lang/test/assets/table-interpreter-meta-inf/valid-correct-table.jv b/libs/extensions/tabular/lang/test/assets/table-interpreter-meta-inf/valid-correct-table.jv new file mode 100644 index 000000000..22bbbae40 --- /dev/null +++ b/libs/extensions/tabular/lang/test/assets/table-interpreter-meta-inf/valid-correct-table.jv @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + block Test oftype TableInterpreter { + header: false; + columns: [ + "name" oftype text, + "version" oftype integer, + ]; + } + + block TestExtractor oftype TestSheetExtractor { + } + + block TestLoader oftype TestTableLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/tabular/lang/test/assets/table-transformer-meta-inf/invalid-input-columns-transform-port-missmatch.jv b/libs/extensions/tabular/lang/test/assets/table-transformer-meta-inf/invalid-input-columns-transform-port-missmatch.jv new file mode 100644 index 000000000..76c886e9c --- /dev/null +++ b/libs/extensions/tabular/lang/test/assets/table-transformer-meta-inf/invalid-input-columns-transform-port-missmatch.jv @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + transform TestTransform { + from Input oftype decimal; + to Output oftype integer; + + Output: ceil(Input); + } + + block Test oftype TableTransformer { + inputColumns: ['input1', 'input2']; + outputColumn: 'output'; + use: TestTransform; + } + + block TestExtractor oftype TestTableExtractor { + } + + block TestLoader oftype TestTableLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/extensions/tabular/lang/test/assets/table-transformer-meta-inf/valid-correct-ports.jv b/libs/extensions/tabular/lang/test/assets/table-transformer-meta-inf/valid-correct-ports.jv new file mode 100644 index 000000000..861d8b360 --- /dev/null +++ b/libs/extensions/tabular/lang/test/assets/table-transformer-meta-inf/valid-correct-ports.jv @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +pipeline Pipeline { + transform TestTransform { + from Input oftype decimal; + to Output oftype integer; + + Output: ceil(Input); + } + + block Test oftype TableTransformer { + inputColumns: ['input1']; + outputColumn: 'output'; + use: TestTransform; + } + + block TestExtractor oftype TestTableExtractor { + } + + block TestLoader oftype TestTableLoader { + } + + TestExtractor -> Test -> TestLoader; +} diff --git a/libs/interpreter-lib/src/parsing-util.ts b/libs/interpreter-lib/src/parsing-util.ts index 8f8fb62f6..a08029a09 100644 --- a/libs/interpreter-lib/src/parsing-util.ts +++ b/libs/interpreter-lib/src/parsing-util.ts @@ -6,11 +6,9 @@ import * as fs from 'fs'; import * as path from 'path'; import { Logger } from '@jvalue/jayvee-execution'; +import { initializeWorkspace } from '@jvalue/jayvee-language-server'; import { AstNode, LangiumDocument, LangiumServices } from 'langium'; -import { - DiagnosticSeverity, - WorkspaceFolder, -} from 'vscode-languageserver-protocol'; +import { DiagnosticSeverity } from 'vscode-languageserver-protocol'; import { URI } from 'vscode-uri'; export enum ExitCode { @@ -63,17 +61,6 @@ export async function extractDocumentFromString( return await validateDocument(document, services, logger); } -/** - * Initializes the workspace with all workspace folders. - * Also loads additional required files, e.g., the standard library - */ -async function initializeWorkspace(services: LangiumServices): Promise { - const workspaceFolders: WorkspaceFolder[] = []; - await services.shared.workspace.WorkspaceManager.initializeWorkspace( - workspaceFolders, - ); -} - export async function validateDocument( document: LangiumDocument, services: LangiumServices, diff --git a/libs/language-server/src/grammar/main.langium b/libs/language-server/src/grammar/main.langium index 55d307284..79dfb50c3 100644 --- a/libs/language-server/src/grammar/main.langium +++ b/libs/language-server/src/grammar/main.langium @@ -13,7 +13,7 @@ import './transform' entry JayveeModel: ( pipelines+=PipelineDefinition - | valuetypes+=ValuetypeDefinition + | valuetypes+=(CustomValuetypeDefinition | BuiltinValuetypeDefinition) | constraints+=ConstraintDefinition | transforms+=TransformDefinition )*; @@ -23,7 +23,7 @@ PipelineDefinition: ( blocks+=BlockDefinition | pipes+=PipeDefinition - | valuetypes+=ValuetypeDefinition + | valuetypes+=CustomValuetypeDefinition | constraints+=ConstraintDefinition | transforms+=TransformDefinition )* diff --git a/libs/language-server/src/grammar/valuetype.langium b/libs/language-server/src/grammar/valuetype.langium index 06e9ee68b..050017c36 100644 --- a/libs/language-server/src/grammar/valuetype.langium +++ b/libs/language-server/src/grammar/valuetype.langium @@ -5,7 +5,10 @@ import './expression' import './terminal' -ValuetypeDefinition: +BuiltinValuetypeDefinition infers ValuetypeDefinition: + isBuiltin?='builtin' 'valuetype' name=ID ';'; + +CustomValuetypeDefinition infers ValuetypeDefinition: 'valuetype' name=ID 'oftype' type=ValuetypeReference '{' 'constraints' ':' constraints=CollectionLiteral ';' '}'; @@ -17,17 +20,4 @@ ValuetypeAssignment: name=STRING 'oftype' type=ValuetypeReference; ValuetypeReference: - (PrimitiveValuetypeKeywordLiteral | ValuetypeDefinitionReference); - -// Workaround as cross-references cannot be mixed with other types -ValuetypeDefinitionReference: reference=[ValuetypeDefinition]; - -PrimitiveValuetypeKeywordLiteral: - keyword=PrimitiveValuetypeKeyword; - -PrimitiveValuetypeKeyword returns string: - 'text' - | 'decimal' - | 'integer' - | 'boolean'; \ No newline at end of file diff --git a/libs/language-server/src/lib/ast/value-definition.spec.ts b/libs/language-server/src/lib/ast/value-definition.spec.ts new file mode 100644 index 000000000..14e362807 --- /dev/null +++ b/libs/language-server/src/lib/ast/value-definition.spec.ts @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { AstNode, LangiumDocument } from 'langium'; +import { NodeFileSystem } from 'langium/node'; + +import { useExtension } from '..'; +import { TestLangExtension } from '../../test/extension'; +import { ParseHelperOptions, parseHelper } from '../../test/langium-utils'; +import { readJvTestAssetHelper } from '../../test/utils'; +import { createJayveeServices } from '../jayvee-module'; + +describe('Parsing of ValuetypeDefinition', () => { + let parse: ( + input: string, + options?: ParseHelperOptions, + ) => Promise>; + + const readJvTestAsset = readJvTestAssetHelper( + __dirname, + '../../test/assets/', + ); + + beforeAll(() => { + useExtension(new TestLangExtension()); + const services = createJayveeServices(NodeFileSystem).Jayvee; + parse = parseHelper(services); + }); + + it('should diagnose error on missing builtin keyword', async () => { + const text = readJvTestAsset( + 'valuetype-definition/invalid-missing-builtin-keyword.jv', + ); + + const document = await parse(text); + expect(document.parseResult.parserErrors.length).toBeGreaterThanOrEqual(1); + expect(document.parseResult.parserErrors[0]?.message).toBe( + "Expecting token of type 'oftype' but found `;`.", + ); + }); + + it('should diagnose error on unallowed body for builtin valuetypes', async () => { + const text = readJvTestAsset( + 'valuetype-definition/invalid-unallowed-builtin-body.jv', + ); + + const document = await parse(text); + expect(document.parseResult.parserErrors.length).toBeGreaterThanOrEqual(1); + expect(document.parseResult.parserErrors[0]?.message).toBe( + "Expecting token of type ';' but found `{`.", + ); + }); +}); diff --git a/libs/language-server/src/lib/ast/wrappers/value-type/atomic-valuetype.ts b/libs/language-server/src/lib/ast/wrappers/value-type/atomic-valuetype.ts index c02fab34b..7de05c0f3 100644 --- a/libs/language-server/src/lib/ast/wrappers/value-type/atomic-valuetype.ts +++ b/libs/language-server/src/lib/ast/wrappers/value-type/atomic-valuetype.ts @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: AGPL-3.0-only +import { strict as assert } from 'assert'; + // eslint-disable-next-line import/no-cycle import { EvaluationContext, @@ -30,7 +32,9 @@ export class AtomicValuetype } getConstraints(context: EvaluationContext): ConstraintDefinition[] { - const constraintCollection = this.astNode.constraints; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + const constraintCollection = this.astNode?.constraints; + assert(constraintCollection !== undefined); const constraintCollectionType = new CollectionValuetype( PrimitiveValuetypes.Constraint, ); diff --git a/libs/language-server/src/lib/ast/wrappers/value-type/primitive/boolean-valuetype.ts b/libs/language-server/src/lib/ast/wrappers/value-type/primitive/boolean-valuetype.ts index 391f34788..b6fe5a840 100644 --- a/libs/language-server/src/lib/ast/wrappers/value-type/primitive/boolean-valuetype.ts +++ b/libs/language-server/src/lib/ast/wrappers/value-type/primitive/boolean-valuetype.ts @@ -3,7 +3,6 @@ // SPDX-License-Identifier: AGPL-3.0-only import { type InternalValueRepresentation } from '../../../expressions/internal-value-representation'; -import { PrimitiveValuetypeKeyword } from '../../../generated/ast'; // eslint-disable-next-line import/no-cycle import { ValuetypeVisitor } from '../valuetype'; @@ -18,7 +17,7 @@ class BooleanValuetypeImpl extends PrimitiveValuetype { return true; } - override getName(): PrimitiveValuetypeKeyword { + override getName(): 'boolean' { return 'boolean'; } @@ -27,6 +26,17 @@ class BooleanValuetypeImpl extends PrimitiveValuetype { ): operandValue is boolean { return typeof operandValue === 'boolean'; } + + override isUserExtendable() { + return true; + } + + override getUserDoc(): string { + return ` +A boolean value. +Examples: true, false +`.trim(); + } } // Only export instance to enforce singleton diff --git a/libs/language-server/src/lib/ast/wrappers/value-type/primitive/decimal-valuetype.ts b/libs/language-server/src/lib/ast/wrappers/value-type/primitive/decimal-valuetype.ts index 97818d91d..86cd05c17 100644 --- a/libs/language-server/src/lib/ast/wrappers/value-type/primitive/decimal-valuetype.ts +++ b/libs/language-server/src/lib/ast/wrappers/value-type/primitive/decimal-valuetype.ts @@ -3,7 +3,6 @@ // SPDX-License-Identifier: AGPL-3.0-only import { type InternalValueRepresentation } from '../../../expressions/internal-value-representation'; -import { PrimitiveValuetypeKeyword } from '../../../generated/ast'; // eslint-disable-next-line import/no-cycle import { ValuetypeVisitor } from '../valuetype'; @@ -18,7 +17,7 @@ class DecimalValuetypeImpl extends PrimitiveValuetype { return true; } - override getName(): PrimitiveValuetypeKeyword { + override getName(): 'decimal' { return 'decimal'; } @@ -27,6 +26,17 @@ class DecimalValuetypeImpl extends PrimitiveValuetype { ): operandValue is number { return typeof operandValue === 'number' && Number.isFinite(operandValue); } + + override isUserExtendable() { + return true; + } + + override getUserDoc(): string { + return ` +A decimal value. +Example: 3.14 +`.trim(); + } } // Only export instance to enforce singleton diff --git a/libs/language-server/src/lib/ast/wrappers/value-type/primitive/integer-valuetype.ts b/libs/language-server/src/lib/ast/wrappers/value-type/primitive/integer-valuetype.ts index 28b32db90..d4f94a0b9 100644 --- a/libs/language-server/src/lib/ast/wrappers/value-type/primitive/integer-valuetype.ts +++ b/libs/language-server/src/lib/ast/wrappers/value-type/primitive/integer-valuetype.ts @@ -3,7 +3,6 @@ // SPDX-License-Identifier: AGPL-3.0-only import { type InternalValueRepresentation } from '../../../expressions/internal-value-representation'; -import { PrimitiveValuetypeKeyword } from '../../../generated/ast'; // eslint-disable-next-line import/no-cycle import { Valuetype, ValuetypeVisitor } from '../valuetype'; @@ -23,7 +22,7 @@ class IntegerValuetypeImpl extends PrimitiveValuetype { return true; } - override getName(): PrimitiveValuetypeKeyword { + override getName(): 'integer' { return 'integer'; } @@ -32,6 +31,17 @@ class IntegerValuetypeImpl extends PrimitiveValuetype { ): operandValue is number { return typeof operandValue === 'number' && Number.isInteger(operandValue); } + + override isUserExtendable() { + return true; + } + + override getUserDoc(): string { + return ` +An integer value. +Example: 3 +`.trim(); + } } // Only export instance to enforce singleton diff --git a/libs/language-server/src/lib/ast/wrappers/value-type/primitive/primitive-valuetype.ts b/libs/language-server/src/lib/ast/wrappers/value-type/primitive/primitive-valuetype.ts index 93da54225..d309951e3 100644 --- a/libs/language-server/src/lib/ast/wrappers/value-type/primitive/primitive-valuetype.ts +++ b/libs/language-server/src/lib/ast/wrappers/value-type/primitive/primitive-valuetype.ts @@ -24,6 +24,22 @@ export abstract class PrimitiveValuetype< protected override doGetSupertype(): undefined { return undefined; } + + /** + * Flag whether an atomic value type can be based on this primitive value type. + */ + isUserExtendable(): boolean { + return false; + } + + /** + * The user documentation for the value type. + * Text only, no comment characters. + * Should be given for all user extendable value types @see isUserExtendable + */ + getUserDoc(): string | undefined { + return undefined; + } } export function isPrimitiveValuetype(v: unknown): v is PrimitiveValuetype { diff --git a/libs/language-server/src/lib/ast/wrappers/value-type/primitive/primitive-valuetypes.ts b/libs/language-server/src/lib/ast/wrappers/value-type/primitive/primitive-valuetypes.ts index 68c71c328..0acc688e8 100644 --- a/libs/language-server/src/lib/ast/wrappers/value-type/primitive/primitive-valuetypes.ts +++ b/libs/language-server/src/lib/ast/wrappers/value-type/primitive/primitive-valuetypes.ts @@ -2,9 +2,9 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { assertUnreachable } from 'langium'; +import { strict as assert } from 'assert'; -import { PrimitiveValuetypeKeywordLiteral } from '../../../generated/ast'; +import { ValuetypeDefinition } from '../../../generated/ast'; // eslint-disable-next-line import/no-cycle import { Boolean, BooleanValuetype } from './boolean-valuetype'; @@ -52,16 +52,16 @@ export const PrimitiveValuetypes: { }; export function createPrimitiveValuetype( - keywordLiteral: PrimitiveValuetypeKeywordLiteral, + builtinValuetype: ValuetypeDefinition, ): PrimitiveValuetype | undefined { + assert(builtinValuetype.isBuiltin); + const name = builtinValuetype.name; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - const keyword = keywordLiteral?.keyword; - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (keyword === undefined) { + if (name === undefined) { return undefined; } - switch (keyword) { + switch (name) { case 'boolean': return Boolean; case 'decimal': @@ -71,6 +71,8 @@ export function createPrimitiveValuetype( case 'text': return Text; default: - assertUnreachable(keyword); + throw new Error( + `Found no PrimitiveValuetype for builtin valuetype "${name}"`, + ); } } diff --git a/libs/language-server/src/lib/ast/wrappers/value-type/primitive/text-valuetype.ts b/libs/language-server/src/lib/ast/wrappers/value-type/primitive/text-valuetype.ts index 3b7ef01f1..e0f80cc10 100644 --- a/libs/language-server/src/lib/ast/wrappers/value-type/primitive/text-valuetype.ts +++ b/libs/language-server/src/lib/ast/wrappers/value-type/primitive/text-valuetype.ts @@ -3,7 +3,6 @@ // SPDX-License-Identifier: AGPL-3.0-only import { type InternalValueRepresentation } from '../../../expressions/internal-value-representation'; -import { PrimitiveValuetypeKeyword } from '../../../generated/ast'; // eslint-disable-next-line import/no-cycle import { ValuetypeVisitor } from '../valuetype'; @@ -18,7 +17,7 @@ class TextValuetypeImpl extends PrimitiveValuetype { return true; } - override getName(): PrimitiveValuetypeKeyword { + override getName(): 'text' { return 'text'; } @@ -27,6 +26,17 @@ class TextValuetypeImpl extends PrimitiveValuetype { ): operandValue is string { return typeof operandValue === 'string'; } + + override isUserExtendable() { + return true; + } + + override getUserDoc(): string { + return ` +A text value. +Example: "Hello World" +`.trim(); + } } // Only export instance to enforce singleton diff --git a/libs/language-server/src/lib/ast/wrappers/value-type/valuetype-util.ts b/libs/language-server/src/lib/ast/wrappers/value-type/valuetype-util.ts index 69aa3ad1f..7eec7d2bc 100644 --- a/libs/language-server/src/lib/ast/wrappers/value-type/valuetype-util.ts +++ b/libs/language-server/src/lib/ast/wrappers/value-type/valuetype-util.ts @@ -7,9 +7,7 @@ import { assertUnreachable } from 'langium'; import { ValuetypeDefinition, ValuetypeReference, - isPrimitiveValuetypeKeywordLiteral, isValuetypeDefinition, - isValuetypeDefinitionReference, isValuetypeReference, } from '../../generated/ast'; @@ -28,19 +26,13 @@ export function createValuetype( if (identifier === undefined) { return undefined; } else if (isValuetypeReference(identifier)) { - if (isPrimitiveValuetypeKeywordLiteral(identifier)) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + const valuetype = identifier?.reference?.ref; + return createValuetype(valuetype); + } else if (isValuetypeDefinition(identifier)) { + if (identifier.isBuiltin) { return createPrimitiveValuetype(identifier); - } else if (isValuetypeDefinitionReference(identifier)) { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - const referenced = identifier?.reference?.ref; - if (referenced === undefined) { - return undefined; - } - - return new AtomicValuetype(referenced); } - assertUnreachable(identifier); - } else if (isValuetypeDefinition(identifier)) { return new AtomicValuetype(identifier); } assertUnreachable(identifier); diff --git a/libs/language-server/src/lib/ast/wrappers/value-type/valuetype.ts b/libs/language-server/src/lib/ast/wrappers/value-type/valuetype.ts index fc0e068d4..1872bba23 100644 --- a/libs/language-server/src/lib/ast/wrappers/value-type/valuetype.ts +++ b/libs/language-server/src/lib/ast/wrappers/value-type/valuetype.ts @@ -3,10 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { type InternalValueRepresentation } from '../../expressions/internal-value-representation'; -import { - PrimitiveValuetypeKeywordLiteral, - ValuetypeDefinition, -} from '../../generated/ast'; +import { ValuetypeDefinition } from '../../generated/ast'; // eslint-disable-next-line import/no-cycle import { AtomicValuetype } from './atomic-valuetype'; @@ -24,9 +21,7 @@ import { type ValuetypeAssignmentValuetype, } from './primitive'; -export type ValuetypeAstNode = - | PrimitiveValuetypeKeywordLiteral - | ValuetypeDefinition; +export type ValuetypeAstNode = ValuetypeDefinition; export interface VisitableValuetype { acceptVisitor(visitor: ValuetypeVisitor): void; diff --git a/libs/language-server/src/lib/builtin-library/index.ts b/libs/language-server/src/lib/builtin-library/index.ts index 29c31493c..d2b2d8907 100644 --- a/libs/language-server/src/lib/builtin-library/index.ts +++ b/libs/language-server/src/lib/builtin-library/index.ts @@ -2,5 +2,5 @@ // // SPDX-License-Identifier: AGPL-3.0-only +export { StdLib } from './stdlib'; export * from './jayvee-workspace-manager'; -export { StdLib } from './generated/stdlib'; diff --git a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts index 726b74ced..8bcadaf6f 100644 --- a/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts +++ b/libs/language-server/src/lib/builtin-library/jayvee-workspace-manager.ts @@ -6,12 +6,13 @@ import { DefaultWorkspaceManager, LangiumDocument, LangiumDocumentFactory, + LangiumServices, LangiumSharedServices, } from 'langium'; import { WorkspaceFolder } from 'vscode-languageserver'; import { URI } from 'vscode-uri'; -import { StdLib } from './generated/stdlib'; +import { StdLib } from './stdlib'; export class JayveeWorkspaceManager extends DefaultWorkspaceManager { private documentFactory: LangiumDocumentFactory; @@ -32,3 +33,16 @@ export class JayveeWorkspaceManager extends DefaultWorkspaceManager { }); } } + +/** + * Initializes the workspace with all workspace folders. + * Also loads additional required files, e.g., the standard library + */ +export async function initializeWorkspace( + services: LangiumServices, +): Promise { + const workspaceFolders: WorkspaceFolder[] = []; + await services.shared.workspace.WorkspaceManager.initializeWorkspace( + workspaceFolders, + ); +} diff --git a/libs/language-server/src/lib/builtin-library/stdlib.ts b/libs/language-server/src/lib/builtin-library/stdlib.ts new file mode 100644 index 000000000..473ec1186 --- /dev/null +++ b/libs/language-server/src/lib/builtin-library/stdlib.ts @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { PrimitiveValuetypes } from '../ast/wrappers/value-type/primitive/primitive-valuetypes'; + +import { PartialStdLib } from './generated/partial-stdlib'; + +export const BuiltinValuetypesLib = { + 'builtin:///stdlib/builtin-valuetypes.jv': Object.values(PrimitiveValuetypes) + .filter((v) => v.isUserExtendable()) + .map( + (valueType) => + `${(valueType.getUserDoc()?.trim().split('\n') ?? []) + .map((t) => '// ' + t) + .join('\n')} +builtin valuetype ${valueType.getName()};`, + ) + .join('\n\n'), +}; + +export const StdLib = { ...PartialStdLib, ...BuiltinValuetypesLib }; diff --git a/libs/language-server/src/lib/docs/jayvee-doc-generator.ts b/libs/language-server/src/lib/docs/jayvee-doc-generator.ts index 00749fd96..8feb857dd 100644 --- a/libs/language-server/src/lib/docs/jayvee-doc-generator.ts +++ b/libs/language-server/src/lib/docs/jayvee-doc-generator.ts @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only +import { Valuetype } from '../ast/wrappers/value-type/valuetype'; import { ConstraintMetaInformation } from '../meta-information'; import { BlockMetaInformation } from '../meta-information/block-meta-inf'; @@ -13,6 +14,10 @@ export interface JayveeConstraintTypeDocGenerator { generateConstraintTypeDoc(metaInf: ConstraintMetaInformation): string; } +export interface JayveeValueTypesDocGenerator { + generateValueTypesDoc(valueTypes: { [name: string]: Valuetype }): string; +} + export interface JayveePropertyDocGenerator { generatePropertyDoc( metaInf: BlockMetaInformation, diff --git a/libs/language-server/src/lib/validation/checks/jayvee-model.spec.ts b/libs/language-server/src/lib/validation/checks/jayvee-model.spec.ts index 31e0ee2d9..ff2020dd1 100644 --- a/libs/language-server/src/lib/validation/checks/jayvee-model.spec.ts +++ b/libs/language-server/src/lib/validation/checks/jayvee-model.spec.ts @@ -106,6 +106,21 @@ describe('Validation of JayveeModel', () => { ); }); + it('should diagnose error on non unique valuetypes (naming collision with builtin)', async () => { + const text = readJvTestAsset( + 'jayvee-model/invalid-duplicate-name-with-builtin-valuetype.jv', + ); + + await parseAndValidateJayveeModel(text); + + expect(validationAcceptorMock).toHaveBeenCalledTimes(2); + expect(validationAcceptorMock).toHaveBeenCalledWith( + 'error', + `The valuetypedefinition name "DuplicateValuetype" needs to be unique.`, + expect.any(Object), + ); + }); + it('should diagnose error on non unique constraints', async () => { const text = readJvTestAsset( 'jayvee-model/invalid-non-unique-constraints.jv', diff --git a/libs/language-server/src/lib/validation/checks/transform-body.ts b/libs/language-server/src/lib/validation/checks/transform-body.ts index 46b6417a3..9470ff99a 100644 --- a/libs/language-server/src/lib/validation/checks/transform-body.ts +++ b/libs/language-server/src/lib/validation/checks/transform-body.ts @@ -7,15 +7,11 @@ */ /* eslint-disable @typescript-eslint/no-unnecessary-condition */ -import { assertUnreachable } from 'langium'; - import { EvaluationContext } from '../../ast/expressions/evaluation'; import { TransformBody, TransformPortDefinition, - isPrimitiveValuetypeKeywordLiteral, isTransformPortDefinition, - isValuetypeDefinitionReference, } from '../../ast/generated/ast'; import { ValidationContext } from '../validation-context'; import { checkUniqueNames } from '../validation-util'; @@ -145,15 +141,10 @@ function checkAreInputsUsed( function isOutputPortComplete( portDefinition: TransformPortDefinition, ): boolean { - const valueType = portDefinition?.valueType; + const valueType = portDefinition?.valueType?.reference?.ref; if (valueType === undefined) { return false; } - if (isPrimitiveValuetypeKeywordLiteral(valueType)) { - return valueType?.keyword !== undefined; - } else if (isValuetypeDefinitionReference(valueType)) { - return valueType?.reference?.ref?.name !== undefined; - } - return assertUnreachable(valueType); + return valueType?.name !== undefined; } diff --git a/libs/language-server/src/lib/validation/checks/valuetype-definition.ts b/libs/language-server/src/lib/validation/checks/valuetype-definition.ts index 64f46ca76..8cceb7dc6 100644 --- a/libs/language-server/src/lib/validation/checks/valuetype-definition.ts +++ b/libs/language-server/src/lib/validation/checks/valuetype-definition.ts @@ -48,6 +48,7 @@ function checkSupertypeCycle( const hasCycle = createValuetype(valuetypeDefinition)?.hasSupertypeCycle() ?? false; if (hasCycle) { + assert(!valuetypeDefinition.isBuiltin); context.accept( 'error', 'Could not construct this valuetype since there is a cycle in the (transitive) "oftype" relation.', diff --git a/libs/language-server/src/test/assets/jayvee-model/invalid-duplicate-name-with-builtin-valuetype.jv b/libs/language-server/src/test/assets/jayvee-model/invalid-duplicate-name-with-builtin-valuetype.jv new file mode 100644 index 000000000..f062b6b92 --- /dev/null +++ b/libs/language-server/src/test/assets/jayvee-model/invalid-duplicate-name-with-builtin-valuetype.jv @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +valuetype DuplicateValuetype oftype text { + constraints: [ + Constraint, + ]; +} + +builtin valuetype DuplicateValuetype; diff --git a/libs/language-server/src/test/assets/valuetype-definition/invalid-missing-builtin-keyword.jv b/libs/language-server/src/test/assets/valuetype-definition/invalid-missing-builtin-keyword.jv new file mode 100644 index 000000000..8bf4adedd --- /dev/null +++ b/libs/language-server/src/test/assets/valuetype-definition/invalid-missing-builtin-keyword.jv @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +valuetype ValueType; diff --git a/libs/language-server/src/test/assets/valuetype-definition/invalid-unallowed-builtin-body.jv b/libs/language-server/src/test/assets/valuetype-definition/invalid-unallowed-builtin-body.jv new file mode 100644 index 000000000..708235e42 --- /dev/null +++ b/libs/language-server/src/test/assets/valuetype-definition/invalid-unallowed-builtin-body.jv @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +builtin valuetype ValueType { + constraints: []; +}; diff --git a/libs/language-server/src/test/langium-utils.ts b/libs/language-server/src/test/langium-utils.ts index e14231e1d..d13aecb14 100644 --- a/libs/language-server/src/test/langium-utils.ts +++ b/libs/language-server/src/test/langium-utils.ts @@ -15,6 +15,8 @@ import { import { Diagnostic } from 'vscode-languageserver'; import { URI } from 'vscode-uri'; +import { initializeWorkspace } from '../lib/builtin-library/jayvee-workspace-manager'; + export interface ParseHelperOptions extends BuildOptions { documentUri?: string; } @@ -39,6 +41,7 @@ export function parseHelper( uri, ); services.shared.workspace.LangiumDocuments.addDocument(document); + await initializeWorkspace(services); await documentBuilder.build([document], options); return document; }; diff --git a/tools/scripts/language-server/generate-stdlib.mjs b/tools/scripts/language-server/generate-stdlib.mjs index 28754ccfe..5f4035809 100644 --- a/tools/scripts/language-server/generate-stdlib.mjs +++ b/tools/scripts/language-server/generate-stdlib.mjs @@ -10,7 +10,7 @@ import { join } from "path"; const projectName = 'language-server'; const stdLibInputPath = 'stdlib'; const outputDirPath = join('lib', 'builtin-library', 'generated'); -const outputFilePath = join(outputDirPath, 'stdlib.ts'); +const outputFilePath = join(outputDirPath, 'partial-stdlib.ts'); // Executing this script: node path/to/generate-stdlib.mjs console.log('Generating stdlib...'); @@ -33,7 +33,7 @@ const stdlibOutput = `/********************************************************* * DO NOT EDIT MANUALLY! ******************************************************************************/ -` + 'export const StdLib = ' + JSON.stringify(libsRecord, null, 2) + ';\n'; +` + 'export const PartialStdLib = ' + JSON.stringify(libsRecord, null, 2) + ';\n'; if (!existsSync(outputDirPath)) { mkdirSync(outputDirPath, {recursive: true}); }