-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: validation for type arguments of named types (#632)
Closes partially #543 ### Summary of Changes Show an error if * the entire type argument list is missing * a type argument is missing * a type parameter is set multiple times * too many type arguments are given. --------- Co-authored-by: megalinter-bot <[email protected]>
- Loading branch information
1 parent
17a5b7a
commit b72768c
Showing
11 changed files
with
334 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/** | ||
* Based on the given count, returns the singular or plural form of the given word. | ||
*/ | ||
export const pluralize = (count: number, singular: string, plural: string = `${singular}s`): string => { | ||
return count === 1 ? singular : plural; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { SdsNamedType } from '../../../generated/ast.js'; | ||
import { ValidationAcceptor } from 'langium'; | ||
import { SafeDsServices } from '../../../safe-ds-module.js'; | ||
import { typeArgumentsOrEmpty, typeParametersOrEmpty } from '../../../helpers/nodeProperties.js'; | ||
import { duplicatesBy } from '../../../helpers/collectionUtils.js'; | ||
import { pluralize } from '../../../helpers/stringUtils.js'; | ||
|
||
export const CODE_NAMED_TYPE_DUPLICATE_TYPE_PARAMETER = 'named-type/duplicate-type-parameter'; | ||
export const CODE_NAMED_TYPE_POSITIONAL_AFTER_NAMED = 'named-type/positional-after-named'; | ||
export const CODE_NAMED_TYPE_TOO_MANY_TYPE_ARGUMENTS = 'named-type/too-many-type-arguments'; | ||
|
||
export const namedTypeMustNotSetTypeParameterMultipleTimes = (services: SafeDsServices) => { | ||
const nodeMapper = services.helpers.NodeMapper; | ||
const typeArgumentToTypeParameterOrUndefined = nodeMapper.typeArgumentToTypeParameterOrUndefined.bind(nodeMapper); | ||
|
||
return (node: SdsNamedType, accept: ValidationAcceptor): void => { | ||
const typeArguments = typeArgumentsOrEmpty(node.typeArgumentList); | ||
const duplicates = duplicatesBy(typeArguments, typeArgumentToTypeParameterOrUndefined); | ||
|
||
for (const duplicate of duplicates) { | ||
const correspondingTypeParameter = typeArgumentToTypeParameterOrUndefined(duplicate)!; | ||
accept('error', `The type parameter '${correspondingTypeParameter.name}' is already set.`, { | ||
node: duplicate, | ||
code: CODE_NAMED_TYPE_DUPLICATE_TYPE_PARAMETER, | ||
}); | ||
} | ||
}; | ||
}; | ||
|
||
export const namedTypeTypeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments = ( | ||
node: SdsNamedType, | ||
accept: ValidationAcceptor, | ||
): void => { | ||
const typeArgumentList = node.typeArgumentList; | ||
if (!typeArgumentList) { | ||
return; | ||
} | ||
|
||
let foundNamed = false; | ||
for (const typeArgument of typeArgumentList.typeArguments) { | ||
if (typeArgument.typeParameter) { | ||
foundNamed = true; | ||
} else if (foundNamed) { | ||
accept('error', 'After the first named type argument all type arguments must be named.', { | ||
node: typeArgument, | ||
code: CODE_NAMED_TYPE_POSITIONAL_AFTER_NAMED, | ||
}); | ||
} | ||
} | ||
}; | ||
|
||
export const namedTypeMustNotHaveTooManyTypeArguments = (node: SdsNamedType, accept: ValidationAcceptor): void => { | ||
// If the declaration is unresolved, we already show another error | ||
if (!node.declaration.ref) { | ||
return; | ||
} | ||
|
||
const typeParameters = typeParametersOrEmpty(node.declaration.ref); | ||
const typeArguments = typeArgumentsOrEmpty(node.typeArgumentList); | ||
|
||
if (typeArguments.length > typeParameters.length) { | ||
const kind = pluralize(typeParameters.length, 'type argument'); | ||
accept('error', `Expected ${typeParameters.length} ${kind} but got ${typeArguments.length}.`, { | ||
node, | ||
property: 'typeArgumentList', | ||
code: CODE_NAMED_TYPE_TOO_MANY_TYPE_ARGUMENTS, | ||
}); | ||
} | ||
}; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { describe, expect, it } from 'vitest'; | ||
import { pluralize } from '../../../src/language/helpers/stringUtils.js'; | ||
|
||
describe('pluralize', () => { | ||
it.each([ | ||
{ | ||
count: 0, | ||
singular: 'apple', | ||
plural: 'apples', | ||
expected: 'apples', | ||
}, | ||
{ | ||
count: 1, | ||
singular: 'apple', | ||
plural: 'apple', | ||
expected: 'apple', | ||
}, | ||
{ | ||
count: 2, | ||
singular: 'apple', | ||
plural: 'apples', | ||
expected: 'apples', | ||
}, | ||
{ | ||
count: 0, | ||
singular: 'apple', | ||
expected: 'apples', | ||
}, | ||
{ | ||
count: 1, | ||
singular: 'apple', | ||
expected: 'apple', | ||
}, | ||
{ | ||
count: 2, | ||
singular: 'apple', | ||
expected: 'apples', | ||
}, | ||
])('should return the singular or plural form based on the count', ({ count, singular, plural, expected }) => { | ||
expect(pluralize(count, singular, plural)).toBe(expected); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
...esources/validation/other/types/type argument lists/duplicate type parameter/main.sdstest
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package tests.validation.other.types.typeArgumentLists.duplicateTypeParameters | ||
|
||
class MyClass<A, B> | ||
|
||
fun myFunction( | ||
f: MyClass< | ||
// $TEST$ no error r"The type parameter '\w+' is already set\." | ||
»Int«, | ||
// $TEST$ error "The type parameter 'A' is already set." | ||
»A = Int« | ||
>, | ||
g: MyClass< | ||
// $TEST$ no error r"The type parameter '\w+' is already set\." | ||
»B = Int«, | ||
// $TEST$ error "The type parameter 'B' is already set." | ||
»B = Int« | ||
>, | ||
h: MyClass< | ||
// $TEST$ no error r"The type parameter '\w+' is already set\." | ||
»A = Int«, | ||
// $TEST$ no error r"The type parameter '\w+' is already set\." | ||
»B = Int« | ||
>, | ||
i: MyClass< | ||
// $TEST$ no error r"The type parameter '\w+' is already set\." | ||
»Int«, | ||
// $TEST$ no error r"The type parameter '\w+' is already set\." | ||
»Int« | ||
>, | ||
j: MyClass< | ||
// $TEST$ no error r"The type parameter '\w+' is already set\." | ||
»Unresolved = Int«, | ||
// $TEST$ no error r"The type parameter '\w+' is already set\." | ||
»Unresolved = Int« | ||
> | ||
) |
17 changes: 17 additions & 0 deletions
17
...resources/validation/other/types/type argument lists/too many type arguments/main.sdstest
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package tests.validation.other.types.typeArgumentLists.tooManyTypeArguments | ||
|
||
class MyClass1<T> | ||
class MyClass2<A, B> | ||
|
||
fun myFunction( | ||
// $TEST$ no error r"Expected \d* type arguments? but got \d*\." | ||
f: MyClass1»<>«, | ||
// $TEST$ no error r"Expected \d* type arguments? but got \d*\." | ||
g: MyClass1»<Int>«, | ||
// $TEST$ error "Expected 1 type argument but got 2." | ||
h: MyClass1»<Int, Int>«, | ||
// $TEST$ error "Expected 2 type arguments but got 3." | ||
i: MyClass2»<Int, Int, Int>«, | ||
// $TEST$ no error r"Expected \d* type arguments? but got \d*\." | ||
j: Unresolved»<Int, Int>« | ||
) |
38 changes: 38 additions & 0 deletions
38
tests/resources/validation/types/named types/missing type argument list/main.sdstest
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package tests.validation.types.namedTypes.missingTypeArgumentList | ||
|
||
class MyClassWithoutTypeParameters | ||
class MyClassWithTypeParameters<T> | ||
|
||
enum MyEnum { | ||
MyEnumVariantWithoutTypeParameters | ||
MyEnumVariantWithTypeParameters<T> | ||
} | ||
|
||
fun myFunction( | ||
// $TEST$ no error r"The type '\w*' is parameterized, so a type argument list must be added\." | ||
a1: »MyClassWithoutTypeParameters«, | ||
// $TEST$ no error r"The type '\w*' is parameterized, so a type argument list must be added\." | ||
b1: »MyClassWithoutTypeParameters«<>, | ||
|
||
// $TEST$ error "The type 'MyClassWithTypeParameters' is parameterized, so a type argument list must be added." | ||
c1: »MyClassWithTypeParameters«, | ||
// $TEST$ no error r"The type '\w*' is parameterized, so a type argument list must be added\." | ||
d1: »MyClassWithTypeParameters«<>, | ||
|
||
|
||
// $TEST$ no error r"The type '\w*' is parameterized, so a type argument list must be added\." | ||
a2: MyEnum.»MyEnumVariantWithoutTypeParameters«, | ||
// $TEST$ no error r"The type '\w*' is parameterized, so a type argument list must be added\." | ||
b2: MyEnum.»MyEnumVariantWithoutTypeParameters«<>, | ||
|
||
// $TEST$ error "The type 'MyEnumVariantWithTypeParameters' is parameterized, so a type argument list must be added." | ||
c2: MyEnum.»MyEnumVariantWithTypeParameters«, | ||
// $TEST$ no error r"The type '\w*' is parameterized, so a type argument list must be added\." | ||
d2: MyEnum.»MyEnumVariantWithTypeParameters«<>, | ||
|
||
|
||
// $TEST$ no error r"The type '\w*' is parameterized, so a type argument list must be added\." | ||
e: »UnresolvedClass«, | ||
// $TEST$ no error r"The type '\w*' is parameterized, so a type argument list must be added\." | ||
f: »UnresolvedClass«<>, | ||
) |
Oops, something went wrong.