Skip to content

Commit

Permalink
feat: error if declarations in the same package have the same name
Browse files Browse the repository at this point in the history
  • Loading branch information
lars-reimann committed Oct 16, 2023
1 parent 801d535 commit b3d7727
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 56 deletions.
120 changes: 72 additions & 48 deletions src/language/validation/names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
SdsSchema,
SdsSegment,
} from '../generated/ast.js';
import { ValidationAcceptor } from 'langium';
import { getDocument, ValidationAcceptor } from 'langium';
import {
blockLambdaResultsOrEmpty,
classMembersOrEmpty,
Expand All @@ -25,6 +25,7 @@ import {
importsOrEmpty,
isStatic,
moduleMembersOrEmpty,
packageNameOrUndefined,
parametersOrEmpty,
placeholdersOrEmpty,
resultsOrEmpty,
Expand All @@ -33,6 +34,7 @@ import {
import { duplicatesBy } from '../helpers/collectionUtils.js';
import { isInPipelineFile, isInStubFile, isInTestFile } from '../helpers/fileExtensions.js';
import { declarationIsAllowedInPipelineFile, declarationIsAllowedInStubFile } from './other/modules.js';
import { SafeDsServices } from '../safe-ds-module.js';

export const CODE_NAME_BLOCK_LAMBDA_PREFIX = 'name/block-lambda-prefix';
export const CODE_NAME_CASING = 'name/casing';
Expand Down Expand Up @@ -146,53 +148,6 @@ const acceptCasingWarning = (
// Uniqueness
// -----------------------------------------------------------------------------

export const modulesMustContainUniqueNames = (node: SdsModule, accept: ValidationAcceptor): void => {
// Names of imported declarations must be unique
const importedDeclarations = importsOrEmpty(node).filter(isSdsQualifiedImport).flatMap(importedDeclarationsOrEmpty);
for (const duplicate of duplicatesBy(importedDeclarations, importedDeclarationName)) {
if (duplicate.alias) {
accept('error', `A declaration with name '${importedDeclarationName(duplicate)}' was imported already.`, {
node: duplicate.alias,
property: 'alias',
code: CODE_NAME_DUPLICATE,
});
} else {
accept('error', `A declaration with name '${importedDeclarationName(duplicate)}' was imported already.`, {
node: duplicate,
property: 'declaration',
code: CODE_NAME_DUPLICATE,
});
}
}

// Names of module members must be unique
if (isInPipelineFile(node)) {
namesMustBeUnique(
moduleMembersOrEmpty(node),
(name) => `A declaration with name '${name}' exists already in this file.`,
accept,
declarationIsAllowedInPipelineFile,
);
} else if (isInStubFile(node)) {
namesMustBeUnique(
moduleMembersOrEmpty(node),
(name) => `A declaration with name '${name}' exists already in this file.`,
accept,
declarationIsAllowedInStubFile,
);
} else if (isInTestFile(node)) {
namesMustBeUnique(
moduleMembersOrEmpty(node),
(name) => `A declaration with name '${name}' exists already in this file.`,
accept,
);
}
};

const importedDeclarationName = (node: SdsImportedDeclaration | undefined): string | undefined => {
return node?.alias?.alias ?? node?.declaration.ref?.name;
};

export const annotationMustContainUniqueNames = (node: SdsAnnotation, accept: ValidationAcceptor): void => {
namesMustBeUnique(parametersOrEmpty(node), (name) => `A parameter with name '${name}' exists already.`, accept);
};
Expand Down Expand Up @@ -268,6 +223,75 @@ export const functionMustContainUniqueNames = (node: SdsFunction, accept: Valida
);
};

export const moduleMemberMustHaveNameThatIsUniqueInPackage = (services: SafeDsServices) => {
const packageManager = services.workspace.PackageManager;

return (node: SdsModule, accept: ValidationAcceptor): void => {
for (const member of moduleMembersOrEmpty(node)) {
const packageName = packageNameOrUndefined(member) ?? '';
const declarationsInPackage = packageManager.getDeclarationsInPackage(packageName);
const memberUri = getDocument(member).uri?.toString();

if (
declarationsInPackage.some((it) => it.name === member.name && it.documentUri.toString() !== memberUri)
) {
accept('error', `Multiple declarations in this package have the name '${member.name}'.`, {
node: member,
property: 'name',
code: CODE_NAME_DUPLICATE,
});
}
}
};
};

export const moduleMustContainUniqueNames = (node: SdsModule, accept: ValidationAcceptor): void => {
// Names of imported declarations must be unique
const importedDeclarations = importsOrEmpty(node).filter(isSdsQualifiedImport).flatMap(importedDeclarationsOrEmpty);
for (const duplicate of duplicatesBy(importedDeclarations, importedDeclarationName)) {
if (duplicate.alias) {
accept('error', `A declaration with name '${importedDeclarationName(duplicate)}' was imported already.`, {
node: duplicate.alias,
property: 'alias',
code: CODE_NAME_DUPLICATE,
});
} else {
accept('error', `A declaration with name '${importedDeclarationName(duplicate)}' was imported already.`, {
node: duplicate,
property: 'declaration',
code: CODE_NAME_DUPLICATE,
});
}
}

// Names of module members must be unique
if (isInPipelineFile(node)) {
namesMustBeUnique(
moduleMembersOrEmpty(node),
(name) => `A declaration with name '${name}' exists already in this file.`,
accept,
declarationIsAllowedInPipelineFile,
);
} else if (isInStubFile(node)) {
namesMustBeUnique(
moduleMembersOrEmpty(node),
(name) => `A declaration with name '${name}' exists already in this file.`,
accept,
declarationIsAllowedInStubFile,
);
} else if (isInTestFile(node)) {
namesMustBeUnique(
moduleMembersOrEmpty(node),
(name) => `A declaration with name '${name}' exists already in this file.`,
accept,
);
}
};

const importedDeclarationName = (node: SdsImportedDeclaration | undefined): string | undefined => {
return node?.alias?.alias ?? node?.declaration.ref?.name;
};

export const pipelineMustContainUniqueNames = (node: SdsPipeline, accept: ValidationAcceptor): void => {
namesMustBeUnique(
placeholdersOrEmpty(node.body),
Expand Down
11 changes: 7 additions & 4 deletions src/language/validation/safe-ds-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import {
enumMustContainUniqueNames,
enumVariantMustContainUniqueNames,
expressionLambdaMustContainUniqueNames,
functionMustContainUniqueNames, modulesMustContainUniqueNames,
functionMustContainUniqueNames, moduleMemberMustHaveNameThatIsUniqueInPackage,
moduleMustContainUniqueNames,
nameMustNotStartWithBlockLambdaPrefix,
nameShouldHaveCorrectCasing,
pipelineMustContainUniqueNames, schemaMustContainUniqueNames,
pipelineMustContainUniqueNames,
schemaMustContainUniqueNames,
segmentMustContainUniqueNames,
} from './names.js';
import {
Expand Down Expand Up @@ -158,8 +160,9 @@ export const registerValidationChecks = function (services: SafeDsServices) {
],
SdsModule: [
moduleDeclarationsMustMatchFileKind,
modulesMustContainUniqueNames,
moduleWithDeclarationsMustStatePackage
moduleMemberMustHaveNameThatIsUniqueInPackage(services),
moduleMustContainUniqueNames,
moduleWithDeclarationsMustStatePackage,
],
SdsNamedType: [
namedTypeDeclarationShouldNotBeDeprecated(services),
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package tests.validation.names.acrossFiles

// $TEST$ error "Multiple declarations in this package have the name 'DuplicateAnnotation'."
annotation »DuplicateAnnotation«
// $TEST$ error "Multiple declarations in this package have the name 'DuplicateClass'."
class »DuplicateClass«
// $TEST$ error "Multiple declarations in this package have the name 'DuplicateEnum'."
enum »DuplicateEnum«
// $TEST$ error "Multiple declarations in this package have the name 'duplicateFunction'."
fun »duplicateFunction«()
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
pipeline »duplicatePipeline« {}
// $TEST$ error "Multiple declarations in this package have the name 'DuplicateSchema'."
schema »DuplicateSchema« {}
// $TEST$ error "Multiple declarations in this package have the name 'duplicatePublicSegment'."
segment »duplicatePublicSegment«() {}
// $TEST$ error "Multiple declarations in this package have the name 'duplicateInternalSegment'."
internal segment »duplicateInternalSegment«() {}
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
private segment »duplicatePrivateSegment«() {}


// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
annotation »UniqueAnnotation«
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
class »UniqueClass«
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
enum »UniqueEnum«
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
fun »uniqueFunction«()
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
pipeline »uniquePipeline« {}
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
schema »UniqueSchema« {}
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
segment »uniquePublicSegment«() {}
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
internal segment »uniqueInternalSegment«() {}
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
private segment »uniquePrivateSegment«() {}


// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
annotation »MyAnnotation«
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
annotation »MyAnnotation«
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
class »MyClass«
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
class »MyClass«
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
enum »MyEnum«
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
enum »MyEnum«
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
fun »myFunction«()
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
fun »myFunction«()
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
pipeline »myPipeline« {}
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
pipeline »myPipeline« {}
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
schema »MySchema« {}
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
schema »MySchema« {}
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
segment »myPublicSegment«() {}
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
segment »myPublicSegment«() {}
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
internal segment »myInternalSegment«() {}
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
internal segment »myInternalSegment«() {}
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
private segment »myPrivateSegment«() {}
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
private segment »myPrivateSegment«() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package tests.validation.names.acrossFiles.other

annotation UniqueAnnotation
class UniqueClass
enum UniqueEnum
fun uniqueFunction()
pipeline uniquePipeline {}
schema UniqueSchema {}
segment uniquePublicSegment() {}
internal segment uniqueInternalSegment() {}
private segment uniquePrivateSegment() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package tests.validation.names.acrossFiles

annotation DuplicateAnnotation
class DuplicateClass
enum DuplicateEnum
fun duplicateFunction()
pipeline duplicatePipeline {}
schema DuplicateSchema {}
segment duplicatePublicSegment() {}
internal segment duplicateInternalSegment() {}
private segment duplicatePrivateSegment() {}
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ fun »duplicateFunction«()
fun »duplicateFunction«()

// $TEST$ no error r"A declaration with name '\w*' exists already in this file\."
pipeline »uniqueTest« {}
pipeline »uniquePipeline« {}
// $TEST$ no error r"A declaration with name '\w*' exists already in this file\."
pipeline »duplicateTest« {}
// $TEST$ error "A declaration with name 'duplicateTest' exists already in this file."
pipeline »duplicateTest« {}
pipeline »duplicatePipeline« {}
// $TEST$ error "A declaration with name 'duplicatePipeline' exists already in this file."
pipeline »duplicatePipeline« {}

// $TEST$ no error r"A declaration with name '\w*' exists already in this file\."
schema »UniqueSchema« {}
Expand Down

0 comments on commit b3d7727

Please sign in to comment.