diff --git a/package.json b/package.json index 020ecf72..3ad8b23e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "wollok-ts", "version": "4.0.4", - "wollokVersion": "3.1.7", + "wollokVersion": "3.1.8", "description": "TypeScript based Wollok language implementation", "repository": "https://github.com/uqbar-project/wollok-ts", "license": "MIT", diff --git a/src/validator.ts b/src/validator.ts index 2dcf71ec..f88451a0 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -161,7 +161,7 @@ export const shouldNotReassignConst = error(node => { }) // TODO: Test if the reference points to the right kind of node -export const missingReference = error>(node => !!node.target ) +export const missingReference = error>(node => !!node.target) export const shouldNotHaveLoopInHierarchy = error(node => !allParents(node).includes(node)) @@ -218,11 +218,15 @@ export const shouldPassValuesToAllAttributes = error( node => getUninitializedAttributesForInstantation(node), ) -export const shouldInitializeAllAttributes = error( - node => isEmpty(getUninitializedAttributes(node)), - node => getUninitializedAttributes(node) +export const shouldInitializeInheritedAttributes = error( + node => isEmpty(getInheritedUninitializedAttributes(node)), + node => [getInheritedUninitializedAttributes(node).join(', ')], ) +export const shouldInitializeSingletonAttribute = error(node => { + return !node.parent.is(Singleton) || !isUninitialized(node.value) +}) + export const shouldNotUseSelf = error(node => { const ancestors = node.ancestors return node.isSynthetic || !ancestors.some(is(Program)) || ancestors.some(is(Singleton)) @@ -395,7 +399,7 @@ export const shouldUseConditionalExpression = warning(node => { elseValue === undefined || ![true, false].includes(thenValue) || thenValue === elseValue) && (!nextSentence || - ![true, false].includes(valueFor(nextSentence)) + ![true, false].includes(valueFor(nextSentence)) ) }) @@ -511,17 +515,27 @@ const getUninitializedAttributesForInstantation = (node: New): string[] => { const target = node.instantiated.target if (!target) return [] const initializers = node.args.map(_ => _.name) - return getUninitializedAttributes(target, initializers) + return getAllUninitializedAttributes(target, initializers) } -const getUninitializedAttributes = (node: Module, initializers: string[] = []) => - node.allFields - .filter(field => { +const getAllUninitializedAttributes = (node: Module, initializers: string[] = []) => + getUninitializedAttributesIn(node, [...node.allFields], initializers) + +const getInheritedUninitializedAttributes = (node: Module, initializers: string[] = []) => + getUninitializedAttributesIn(node, [...node.allFields.filter(f => f.parent !== node)], initializers) + + +const getUninitializedAttributesIn = (node: Module, fields: Field[], initializers: string[] = []) => + fields. + filter(field => { const value = node.defaultValueFor(field) - return value.isSynthetic && value.is(Literal) && value.isNull() && !initializers.includes(field.name) + return isUninitialized(value) && !initializers.includes(field.name) }) .map(field => field.name) + +const isUninitialized = (value: Expression) => value.isSynthetic && value.is(Literal) && value.isNull() + const isBooleanLiteral = (node: Expression, value: boolean) => node.is(Literal) && node.value === value const targetSupertypes = (node: Class | Singleton) => node.supertypes.map(_ => _?.reference.target) @@ -560,10 +574,10 @@ const referencesSingleton = (node: Expression) => node.is(Reference) && node.tar const isBooleanOrUnknownType = (node: Node): boolean => match(node)( when(Literal)(condition => condition.value === true || condition.value === false), - when(Send)( _ => true), // tackled in a different validator - when(Super)( _ => true), - when(Reference)( condition => !condition.target?.is(Singleton)), - when(Node)( _ => false), + when(Send)(_ => true), // tackled in a different validator + when(Super)(_ => true), + when(Reference)(condition => !condition.target?.is(Singleton)), + when(Node)(_ => false), ) const valueFor: any | undefined = (node: Node) => @@ -746,9 +760,9 @@ const validationsByKind = (node: Node): Record> => match when(Program)(() => ({ nameShouldNotBeKeyword, shouldNotUseReservedWords, shouldMatchFileExtension, shouldNotDuplicateEntities })), when(Test)(() => ({ shouldHaveNonEmptyName, shouldNotMarkMoreThanOneOnlyTest, shouldHaveAssertInTest, shouldMatchFileExtension })), when(Class)(() => ({ nameShouldBeginWithUppercase, nameShouldNotBeKeyword, shouldNotHaveLoopInHierarchy, linearizationShouldNotRepeatNamedArguments, shouldNotDefineMoreThanOneSuperclass, superclassShouldBeLastInLinearization, shouldNotDuplicateGlobalDefinitions, shouldNotDuplicateVariablesInLinearization, shouldImplementAllMethodsInHierarchy, shouldNotUseReservedWords, shouldNotDuplicateEntities })), - when(Singleton)(() => ({ nameShouldBeginWithLowercase, inlineSingletonShouldBeAnonymous, topLevelSingletonShouldHaveAName, nameShouldNotBeKeyword, shouldInitializeAllAttributes, linearizationShouldNotRepeatNamedArguments, shouldNotDefineMoreThanOneSuperclass, superclassShouldBeLastInLinearization, shouldNotDuplicateGlobalDefinitions, shouldNotDuplicateVariablesInLinearization, shouldImplementAbstractMethods, shouldImplementAllMethodsInHierarchy, shouldNotUseReservedWords, shouldNotDuplicateEntities })), + when(Singleton)(() => ({ nameShouldBeginWithLowercase, inlineSingletonShouldBeAnonymous, topLevelSingletonShouldHaveAName, nameShouldNotBeKeyword, shouldInitializeInheritedAttributes, linearizationShouldNotRepeatNamedArguments, shouldNotDefineMoreThanOneSuperclass, superclassShouldBeLastInLinearization, shouldNotDuplicateGlobalDefinitions, shouldNotDuplicateVariablesInLinearization, shouldImplementAbstractMethods, shouldImplementAllMethodsInHierarchy, shouldNotUseReservedWords, shouldNotDuplicateEntities })), when(Mixin)(() => ({ nameShouldBeginWithUppercase, shouldNotHaveLoopInHierarchy, shouldOnlyInheritFromMixin, shouldNotDuplicateGlobalDefinitions, shouldNotDuplicateVariablesInLinearization, shouldNotDuplicateEntities })), - when(Field)(() => ({ nameShouldBeginWithLowercase, shouldNotAssignToItselfInDeclaration, nameShouldNotBeKeyword, shouldNotDuplicateFields, shouldNotUseReservedWords, shouldNotDefineUnusedVariables, shouldDefineConstInsteadOfVar })), + when(Field)(() => ({ nameShouldBeginWithLowercase, shouldNotAssignToItselfInDeclaration, nameShouldNotBeKeyword, shouldNotDuplicateFields, shouldNotUseReservedWords, shouldNotDefineUnusedVariables, shouldDefineConstInsteadOfVar, shouldInitializeSingletonAttribute })), when(Method)(() => ({ onlyLastParameterCanBeVarArg, nameShouldNotBeKeyword, methodShouldHaveDifferentSignature, shouldNotOnlyCallToSuper, shouldUseOverrideKeyword, possiblyReturningBlock, shouldNotUseOverride, shouldMatchSuperclassReturnValue, shouldNotDefineNativeMethodsOnUnnamedSingleton, overridingMethodShouldHaveABody, getterMethodShouldReturnAValue })), when(Variable)(() => ({ nameShouldBeginWithLowercase, nameShouldNotBeKeyword, shouldNotAssignToItselfInDeclaration, shouldNotDuplicateLocalVariables, shouldNotDuplicateGlobalDefinitions, shouldNotDefineGlobalMutableVariables, shouldNotUseReservedWords, shouldInitializeGlobalReference, shouldDefineConstInsteadOfVar, shouldNotDuplicateEntities })), when(Assignment)(() => ({ shouldNotAssignToItself, shouldNotReassignConst })), diff --git a/test/validator.test.ts b/test/validator.test.ts index 51751f30..6eb705e6 100644 --- a/test/validator.test.ts +++ b/test/validator.test.ts @@ -4,9 +4,9 @@ import { readFileSync } from 'fs' import globby from 'globby' import { join } from 'path' import { Annotation, buildEnvironment } from '../src' -import { notEmpty } from '../src/extensions' +import { List, notEmpty } from '../src/extensions' import validate from '../src/validator' -import { Node, Problem } from './../src/model' +import { Class, Literal, Node, Problem, Reference } from './../src/model' const TESTS_PATH = 'language/test/validations' @@ -50,6 +50,7 @@ describe('Wollok Validations', () => { for (const expectedProblem of expectedProblems) { const code = expectedProblem.args.get('code')! const level = expectedProblem.args.get('level') + const values = expectedProblem.args.get('values') if (!code) fail('Missing required "code" argument in @Expect annotation') @@ -64,6 +65,12 @@ describe('Wollok Validations', () => { if (level && effectiveProblem.level !== level) fail(`Expected ${code} to be ${level} but was ${effectiveProblem.level} at ${errorLocation(node)}`) + + if (values) { + const stringValues = (values as [Reference, List>])[1].map(v => v.value) + if (stringValues.join('||') !== effectiveProblem.values.join('||')) + fail(`Expected ${code} to have ${JSON.stringify(stringValues)} but was ${JSON.stringify(effectiveProblem.values)} at ${errorLocation(node)}`) + } } for (const problem of problems) {