Skip to content

Commit 2803305

Browse files
feat: port additional checks (#567)
Closes partially #543 ### Summary of Changes Port additional checks from old Xtext version: * Template string without expression between string parts * Yield in pipeline * Missing type hints * Missing package in module with declarations * Declarations in module that are not allowed by the file type --------- Co-authored-by: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com>
1 parent c26d33a commit 2803305

File tree

36 files changed

+386
-17
lines changed

36 files changed

+386
-17
lines changed

src/language/validation/names.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { SdsDeclaration } from '../generated/ast.js';
22
import { ValidationAcceptor } from 'langium';
33

4-
export const CODE_NAMES_BLOCK_LAMBDA_PREFIX = 'names/block-lambda-prefix';
5-
export const CODE_NAMES_CASING = 'names/casing';
4+
export const CODE_NAME_BLOCK_LAMBDA_PREFIX = 'name/block-lambda-prefix';
5+
export const CODE_NAME_CASING = 'name/casing';
66

77
export const nameMustNotStartWithBlockLambdaPrefix = (node: SdsDeclaration, accept: ValidationAcceptor) => {
88
const name = node.name ?? '';
@@ -14,7 +14,7 @@ export const nameMustNotStartWithBlockLambdaPrefix = (node: SdsDeclaration, acce
1414
{
1515
node,
1616
property: 'name',
17-
code: CODE_NAMES_BLOCK_LAMBDA_PREFIX,
17+
code: CODE_NAME_BLOCK_LAMBDA_PREFIX,
1818
},
1919
);
2020
}
@@ -43,7 +43,7 @@ export const nameShouldHaveCorrectCasing = (node: SdsDeclaration, accept: Valida
4343
accept('warning', 'All segments of the qualified name of a package should be lowerCamelCase.', {
4444
node,
4545
property: 'name',
46-
code: CODE_NAMES_CASING,
46+
code: CODE_NAME_CASING,
4747
});
4848
}
4949
return;
@@ -95,6 +95,6 @@ const acceptCasingWarning = (
9595
accept('warning', `Names of ${nodeName} should be ${expectedCasing}.`, {
9696
node,
9797
property: 'name',
98-
code: CODE_NAMES_CASING,
98+
code: CODE_NAME_CASING,
9999
});
100100
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { isSdsTemplateStringPart, SdsTemplateString } from '../../../generated/ast.js';
2+
import { ValidationAcceptor } from 'langium';
3+
4+
export const CODE_TEMPLATE_STRING_MISSING_TEMPLATE_EXPRESSION = 'template-string/missing-template-expression';
5+
6+
export const templateStringMustHaveExpressionBetweenTwoStringParts = (
7+
node: SdsTemplateString,
8+
accept: ValidationAcceptor,
9+
): void => {
10+
for (let i = 0; i < node.expressions.length - 1; i++) {
11+
const first = node.expressions[i];
12+
const second = node.expressions[i + 1];
13+
14+
if (isSdsTemplateStringPart(first) && isSdsTemplateStringPart(second)) {
15+
accept('error', 'There must be an expression between two string parts.', {
16+
node: second,
17+
code: CODE_TEMPLATE_STRING_MISSING_TEMPLATE_EXPRESSION,
18+
});
19+
}
20+
}
21+
};
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { ValidationAcceptor } from 'langium';
2+
import { isSdsDeclaration, isSdsPipeline, isSdsSegment, SdsModule } from '../../generated/ast.js';
3+
import { isInPipelineFile, isInStubFile } from '../../constants/fileExtensions.js';
4+
5+
export const CODE_MODULE_MISSING_PACKAGE = 'module/missing-package';
6+
7+
export const CODE_MODULE_FORBIDDEN_IN_PIPELINE_FILE = 'module/forbidden-in-pipeline-file';
8+
9+
export const CODE_MODULE_FORBIDDEN_IN_STUB_FILE = 'module/forbidden-in-stub-file';
10+
11+
export const moduleWithDeclarationsMustStatePackage = (node: SdsModule, accept: ValidationAcceptor): void => {
12+
if (!node.name) {
13+
const declarations = node.members.filter(isSdsDeclaration);
14+
if (declarations.length > 0) {
15+
accept('error', 'A module with declarations must state its package.', {
16+
node: declarations[0],
17+
property: 'name',
18+
code: CODE_MODULE_MISSING_PACKAGE,
19+
});
20+
}
21+
}
22+
};
23+
24+
export const moduleDeclarationsMustMatchFileKind = (node: SdsModule, accept: ValidationAcceptor): void => {
25+
const declarations = node.members.filter(isSdsDeclaration);
26+
27+
if (isInPipelineFile(node)) {
28+
for (const declaration of declarations) {
29+
if (!isSdsPipeline(declaration) && !isSdsSegment(declaration)) {
30+
accept('error', 'A pipeline file must only declare pipelines and segments.', {
31+
node: declaration,
32+
property: 'name',
33+
code: CODE_MODULE_FORBIDDEN_IN_PIPELINE_FILE,
34+
});
35+
}
36+
}
37+
} else if (isInStubFile(node)) {
38+
for (const declaration of declarations) {
39+
if (isSdsPipeline(declaration) || isSdsSegment(declaration)) {
40+
accept('error', 'A stub file must not declare pipelines or segments.', {
41+
node: declaration,
42+
property: 'name',
43+
code: CODE_MODULE_FORBIDDEN_IN_STUB_FILE,
44+
});
45+
}
46+
}
47+
}
48+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { isSdsPipeline, SdsYield } from '../../../generated/ast.js';
2+
import { getContainerOfType, ValidationAcceptor } from 'langium';
3+
4+
export const CODE_ASSIGMENT_YIELD_FORBIDDEN_IN_PIPELINE = 'assignment/yield-forbidden-in-pipeline';
5+
6+
export const yieldMustNotBeUsedInPipeline = (node: SdsYield, accept: ValidationAcceptor): void => {
7+
const containingPipeline = getContainerOfType(node, isSdsPipeline);
8+
9+
if (containingPipeline) {
10+
accept('error', 'Yield must not be used in a pipeline.', {
11+
node,
12+
code: CODE_ASSIGMENT_YIELD_FORBIDDEN_IN_PIPELINE,
13+
});
14+
}
15+
};

src/language/validation/safe-ds-validator.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import {
1414
functionTypeParameterListShouldNotBeEmpty,
1515
segmentResultListShouldNotBeEmpty,
1616
unionTypeShouldNotHaveASingularTypeArgument,
17-
} from './unnecessarySyntax.js';
17+
} from './style.js';
18+
import { templateStringMustHaveExpressionBetweenTwoStringParts } from './other/expressions/templateStrings.js';
19+
import { yieldMustNotBeUsedInPipeline } from './other/statements/assignments.js';
20+
import { attributeMustHaveTypeHint, parameterMustHaveTypeHint, resultMustHaveTypeHint } from './types.js';
21+
import { moduleDeclarationsMustMatchFileKind, moduleWithDeclarationsMustStatePackage } from './other/modules.js';
1822

1923
/**
2024
* Register custom validation checks.
@@ -25,13 +29,19 @@ export const registerValidationChecks = function (services: SafeDsServices) {
2529
const checks: ValidationChecks<SafeDsAstType> = {
2630
SdsAssignment: [assignmentShouldHaveMoreThanWildcardsAsAssignees],
2731
SdsAnnotation: [annotationParameterListShouldNotBeEmpty],
32+
SdsAttribute: [attributeMustHaveTypeHint],
2833
SdsClass: [classBodyShouldNotBeEmpty, classTypeParameterListShouldNotBeEmpty],
2934
SdsDeclaration: [nameMustNotStartWithBlockLambdaPrefix, nameShouldHaveCorrectCasing],
3035
SdsEnum: [enumBodyShouldNotBeEmpty],
3136
SdsEnumVariant: [enumVariantParameterListShouldNotBeEmpty, enumVariantTypeParameterListShouldNotBeEmpty],
3237
SdsFunction: [functionResultListShouldNotBeEmpty, functionTypeParameterListShouldNotBeEmpty],
38+
SdsModule: [moduleDeclarationsMustMatchFileKind, moduleWithDeclarationsMustStatePackage],
39+
SdsParameter: [parameterMustHaveTypeHint],
40+
SdsResult: [resultMustHaveTypeHint],
3341
SdsSegment: [segmentResultListShouldNotBeEmpty],
42+
SdsTemplateString: [templateStringMustHaveExpressionBetweenTwoStringParts],
3443
SdsUnionType: [unionTypeShouldNotHaveASingularTypeArgument],
44+
SdsYield: [yieldMustNotBeUsedInPipeline],
3545
};
3646
registry.register(checks, validator);
3747
};
File renamed without changes.

src/language/validation/types.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { getContainerOfType, ValidationAcceptor } from 'langium';
2+
import { isSdsCallable, isSdsLambda, SdsAttribute, SdsParameter, SdsResult } from '../generated/ast.js';
3+
4+
export const CODE_TYPE_MISSING_TYPE_HINT = 'type/missing-type-hint';
5+
6+
// -----------------------------------------------------------------------------
7+
// Missing type hints
8+
// -----------------------------------------------------------------------------
9+
10+
export const attributeMustHaveTypeHint = (node: SdsAttribute, accept: ValidationAcceptor): void => {
11+
if (!node.type) {
12+
accept('error', 'An attribute must have a type hint.', {
13+
node,
14+
property: 'name',
15+
code: CODE_TYPE_MISSING_TYPE_HINT,
16+
});
17+
}
18+
};
19+
20+
export const parameterMustHaveTypeHint = (node: SdsParameter, accept: ValidationAcceptor): void => {
21+
if (!node.type) {
22+
const containingCallable = getContainerOfType(node, isSdsCallable);
23+
24+
if (!isSdsLambda(containingCallable)) {
25+
accept('error', 'A parameter must have a type hint.', {
26+
node,
27+
property: 'name',
28+
code: CODE_TYPE_MISSING_TYPE_HINT,
29+
});
30+
}
31+
}
32+
};
33+
34+
export const resultMustHaveTypeHint = (node: SdsResult, accept: ValidationAcceptor): void => {
35+
if (!node.type) {
36+
accept('error', 'A result must have a type hint.', {
37+
node,
38+
property: 'name',
39+
code: CODE_TYPE_MISSING_TYPE_HINT,
40+
});
41+
}
42+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package tests.validation.other.expressions.templateStrings.missingTemplateExpression
2+
3+
pipeline test {
4+
// $TEST$ error "There must be an expression between two string parts."
5+
// $TEST$ error "There must be an expression between two string parts."
6+
"start {{ »}} inner {{« »}} end"«;
7+
8+
// $TEST$ no error "There must be an expression between two string parts."
9+
// $TEST$ no error "There must be an expression between two string parts."
10+
"start {{ 1 »}} inner {{« 1 »}} end"«;
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package tests.validation.other.modules.declarationsInPipelineFiles
2+
3+
// $TEST$ error "A pipeline file must only declare pipelines and segments."
4+
annotation »MyAnnotation«
5+
// $TEST$ error "A pipeline file must only declare pipelines and segments."
6+
class »MyClass« {
7+
8+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
9+
attr »a«: Int
10+
11+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
12+
class »MyNestedClass«
13+
14+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
15+
enum »MyEnum«
16+
17+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
18+
fun »myFunction«()
19+
}
20+
// $TEST$ error "A pipeline file must only declare pipelines and segments."
21+
enum »MyEnum« {
22+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
23+
»MyEnumInstance«
24+
}
25+
// $TEST$ error "A pipeline file must only declare pipelines and segments."
26+
fun »myFunction«()
27+
// $TEST$ error "A pipeline file must only declare pipelines and segments."
28+
schema »MySchema« {}
29+
30+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
31+
pipeline »myPipeline« {}
32+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
33+
segment »mySegment«() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package tests.validation.other.modules.declarationsInStubFiles
2+
3+
// $TEST$ error "A stub file must not declare pipelines or segments."
4+
pipeline »myPipeline« {}
5+
// $TEST$ error "A stub file must not declare pipelines or segments."
6+
segment »mySegment«() {}
7+
8+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
9+
annotation »MyAnnotation«
10+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
11+
class »MyClass« {
12+
13+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
14+
attr »a«: Int
15+
16+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
17+
class »MyNestedClass«
18+
19+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
20+
enum »MyEnum«
21+
22+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
23+
fun »myFunction«()
24+
}
25+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
26+
enum »MyEnum« {
27+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
28+
»MyEnumInstance«
29+
}
30+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
31+
fun »myFunction«()
32+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
33+
schema »MySchema« {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package tests.validation.other.modules.declarationsInTestFiles
2+
3+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
4+
annotation »MyAnnotation«
5+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
6+
class »MyClass« {
7+
8+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
9+
attr »a«: Int
10+
11+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
12+
class »MyNestedClass«
13+
14+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
15+
enum »MyEnum«
16+
17+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
18+
fun »myFunction«()
19+
}
20+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
21+
enum »MyEnum« {
22+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
23+
»MyEnumInstance«
24+
}
25+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
26+
fun »myFunction«()
27+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
28+
schema »MySchema« {}
29+
30+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
31+
pipeline »myPipeline« {}
32+
// $TEST$ no error "A pipeline file must only declare pipelines and segments."
33+
segment »mySegment«() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// $TEST$ no error "A module with declarations must state its package."
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// $TEST$ no error "A module with declarations must state its package."
2+
3+
@AnnotationCall
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// $TEST$ no error "A module with declarations must state its package."
2+
3+
import myPackage
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package tests.validation.other.modules.mustStatePackage
2+
3+
// $TEST$ no error "A module with declarations must state its package."
4+
segment »s«() {}
5+
6+
// $TEST$ no error "A module with declarations must state its package."
7+
segment »t«() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// $TEST$ error "A module with declarations must state its package."
2+
segment »s«() {}
3+
4+
// $TEST$ no error "A module with declarations must state its package."
5+
segment »t«() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// $TEST$ no error "A module with declarations must state its package."
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// $TEST$ no error "A module with declarations must state its package."
2+
3+
@AnnotationCall
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// $TEST$ no error "A module with declarations must state its package."
2+
3+
import myPackage
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package tests.validation.other.modules.mustStatePackage
2+
3+
// $TEST$ no error "A module with declarations must state its package."
4+
class »C«
5+
6+
// $TEST$ no error "A module with declarations must state its package."
7+
class »D«
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// $TEST$ error "A module with declarations must state its package."
2+
class »C«
3+
4+
// $TEST$ no error "A module with declarations must state its package."
5+
class »D«
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package tests.validation.other.statements.assignments.yieldForbiddenInPipeline
2+
3+
segment s() {
4+
// $TEST$ no error "Yield must not be used in a pipeline."
5+
»yield a« = 1;
6+
}
7+
8+
pipeline p {
9+
// $TEST$ error "Yield must not be used in a pipeline."
10+
»yield a« = 1;
11+
12+
() {
13+
// $TEST$ no error "Yield must not be used in a pipeline."
14+
»yield a« = 1;
15+
};
16+
}

tests/resources/validation/unnecessary syntax/unnecessary assignment/main.sdstest tests/resources/validation/style/unnecessary assignment/main.sdstest

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package tests.validation.unnecessarySyntax.unnecessaryAssignment
1+
package tests.validation.style.unnecessaryAssignment
22

33
fun f() -> (a: Int, b: Int)
44

tests/resources/validation/unnecessary syntax/unnecessary body in class/main.sdstest tests/resources/validation/style/unnecessary body in class/main.sdstest

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package tests.validation.unnecessarySyntax.unnecessaryBodyInClass
1+
package tests.validation.style.unnecessaryBodyInClass
22

33
// $TEST$ info "This body can be removed."
44
class MyClass1 »{}«

tests/resources/validation/unnecessary syntax/unnecessary body in enum/main.sdstest tests/resources/validation/style/unnecessary body in enum/main.sdstest

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package tests.validation.unnecessarySyntax.unnecessaryBodyInEnum
1+
package tests.validation.style.unnecessaryBodyInEnum
22

33
// $TEST$ info "This body can be removed."
44
enum MyEnum1 »{}«

0 commit comments

Comments
 (0)