From 264f86278b8d49bd25f1efeaeba9b61d161a363a Mon Sep 17 00:00:00 2001 From: palumbon Date: Wed, 5 Jul 2023 12:20:53 +0200 Subject: [PATCH 1/4] Assert values in validation tests --- test/validator.test.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/validator.test.ts b/test/validator.test.ts index 9b4d36f1..fa25639a 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) { From 6182334ad28ceb6887cce6cb8f47d22282571c5c Mon Sep 17 00:00:00 2001 From: palumbon Date: Wed, 5 Jul 2023 12:20:53 +0200 Subject: [PATCH 2/4] Fixing shouldInitializeAllAttributes - Converted into shouldInitializeInheritedAttributes - Adding shouldInitializeSingletonAttribute --- src/validator.ts | 58 ++++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/src/validator.ts b/src/validator.ts index 67c310f0..e777b278 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -20,9 +20,11 @@ // - Good default for simple problems, but with a config object for more complex, so we know what is each parameter import { count, TypeDefinition, duplicates, is, isEmpty, last, List, match, notEmpty, when } from './extensions' // - Unified problem type -import { Assignment, Body, Catch, Class, Code, Describe, Entity, Expression, Field, If, Import, +import { + Assignment, Body, Catch, Class, Code, Describe, Entity, Expression, Field, If, Import, Level, Literal, Method, Mixin, Module, NamedArgument, New, Node, Package, Parameter, ParameterizedType, Problem, - Program, Reference, Return, Self, Send, Sentence, Singleton, SourceIndex, SourceMap, Super, Test, Throw, Try, Variable } from './model' + Program, Reference, Return, Self, Send, Sentence, Singleton, SourceIndex, SourceMap, Super, Test, Throw, Try, Variable +} from './model' const { entries } = Object @@ -116,7 +118,7 @@ export const nameShouldBeginWithLowercase = nameMatches(/^[a-z_<]/) export const nameShouldNotBeKeyword = error(node => !KEYWORDS.includes(node.name || ''), -node => [node.name || ''], + node => [node.name || ''], ) export const inlineSingletonShouldBeAnonymous = error( @@ -161,7 +163,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 +220,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,8 +401,8 @@ 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 +517,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 +576,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 +762,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 })), @@ -766,7 +782,7 @@ const validationsByKind = (node: Node): Record> => match export default (target: Node): List => target.reduce((found, node) => { return [ ...found, - ...node.problems?.map(({ code }) => ({ code, level: 'error', node, values: [], source: node.sourceMap } as const) ) ?? [], + ...node.problems?.map(({ code }) => ({ code, level: 'error', node, values: [], source: node.sourceMap } as const)) ?? [], ...entries(validationsByKind(node)) .map(([code, validation]) => validation(node, code)!) .filter(result => result !== null), From 775587992451b587285eb621ac5fec92d20a8202 Mon Sep 17 00:00:00 2001 From: palumbon Date: Sat, 12 Aug 2023 13:07:53 +0200 Subject: [PATCH 3/4] eslint --- src/validator.ts | 10 ++++------ test/game.test.ts | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/validator.ts b/src/validator.ts index e777b278..d5f91268 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -20,11 +20,9 @@ // - Good default for simple problems, but with a config object for more complex, so we know what is each parameter import { count, TypeDefinition, duplicates, is, isEmpty, last, List, match, notEmpty, when } from './extensions' // - Unified problem type -import { - Assignment, Body, Catch, Class, Code, Describe, Entity, Expression, Field, If, Import, +import { Assignment, Body, Catch, Class, Code, Describe, Entity, Expression, Field, If, Import, Level, Literal, Method, Mixin, Module, NamedArgument, New, Node, Package, Parameter, ParameterizedType, Problem, - Program, Reference, Return, Self, Send, Sentence, Singleton, SourceIndex, SourceMap, Super, Test, Throw, Try, Variable -} from './model' + Program, Reference, Return, Self, Send, Sentence, Singleton, SourceIndex, SourceMap, Super, Test, Throw, Try, Variable } from './model' const { entries } = Object @@ -118,7 +116,7 @@ export const nameShouldBeginWithLowercase = nameMatches(/^[a-z_<]/) export const nameShouldNotBeKeyword = error(node => !KEYWORDS.includes(node.name || ''), - node => [node.name || ''], +node => [node.name || ''], ) export const inlineSingletonShouldBeAnonymous = error( @@ -402,7 +400,7 @@ export const shouldUseConditionalExpression = warning(node => { ![true, false].includes(thenValue) || thenValue === elseValue) && (!nextSentence || ![true, false].includes(valueFor(nextSentence)) - ) + ) }) diff --git a/test/game.test.ts b/test/game.test.ts index 7176b055..8b3b55c1 100644 --- a/test/game.test.ts +++ b/test/game.test.ts @@ -14,7 +14,7 @@ describe('Wollok Game', () => { describe('actions', () => { let environment: Environment - let interpreter: Interpreter + let interpreter: Interpreter before(async () => { From c1bd2e43140aea1438599a55617c2c4e0e302099 Mon Sep 17 00:00:00 2001 From: palumbon Date: Sat, 12 Aug 2023 13:08:22 +0200 Subject: [PATCH 4/4] Bump Wollok version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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",