diff --git a/src/model.ts b/src/model.ts index edec5294..d22d606d 100644 --- a/src/model.ts +++ b/src/model.ts @@ -41,6 +41,7 @@ export class SourceMap { toString(): string { return `[${this.start}, ${this.end}]` } covers(offset: number): boolean { return this.start.offset <= offset && this.end.offset >= offset } + includes(other: SourceMap): boolean { return this.start.offset <= other.start.offset && this.end.offset >= other.end.offset } } export class Annotation { diff --git a/src/parser.ts b/src/parser.ts index afe33987..ac4233e0 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,4 +1,4 @@ -import Parsimmon, { alt as alt_parser, index, lazy, makeSuccess, notFollowedBy, of, Parser, regex, seq, seqObj, string, whitespace, any, Index } from 'parsimmon' +import Parsimmon, { alt as alt_parser, index, lazy, makeSuccess, notFollowedBy, of, Parser, regex, seq, seqObj, string, whitespace, any, Index, newline } from 'parsimmon' import unraw from 'unraw' import { BaseProblem, SourceIndex, Assignment as AssignmentNode, Body as BodyNode, Catch as CatchNode, Class as ClassNode, Describe as DescribeNode, Entity as EntityNode, Expression as ExpressionNode, Field as FieldNode, If as IfNode, Import as ImportNode, Literal as LiteralNode, Method as MethodNode, Mixin as MixinNode, Name, NamedArgument as NamedArgumentNode, New as NewNode, Node, Package as PackageNode, Parameter as ParameterNode, Program as ProgramNode, Reference as ReferenceNode, Return as ReturnNode, Self as SelfNode, Send as SendNode, Sentence as SentenceNode, Singleton as SingletonNode, Super as SuperNode, Test as TestNode, Throw as ThrowNode, Try as TryNode, Variable as VariableNode, SourceMap, Closure as ClosureNode, ParameterizedType as ParameterizedTypeNode, Level, LiteralValue, Annotation } from './model' import { List, mapObject, discriminate, is } from './extensions' @@ -58,7 +58,7 @@ const error = (code: string) => (...safewords: string[]) => { alt( skippableContext, comment, - notFollowedBy(alt(key('}'), ...breakpoints)).then(any), + notFollowedBy(alt(key('}'), newline, ...breakpoints)).then(any), ) ) @@ -178,6 +178,17 @@ export const Body: Parser = node(BodyNode)(() => obj({ sentences: alt(Sentence.skip(__), sentenceError).many() }).wrap(key('{'), key('}')).map(recover) ) +export const ExpressionBody: Parser = node(BodyNode)(() => { + return obj( + { + sentences: alt(Expression.skip(__).map(value => { + return new ReturnNode({ value }) + }), sentenceError).times(1), + } + ).wrap(_, _).map(recover) +}) + + const inlineableBody: Parser = Body.or( node(BodyNode)(() => obj({ sentences: Sentence.times(1) })).map(body => body.copy({ @@ -315,10 +326,7 @@ export const Method: Parser = node(MethodNode)(() => name: key('method').then(alt(name, operator(ALL_OPERATORS))), parameters, body: alt( - key('=').then(Expression.map(value => new BodyNode({ - sentences: [new ReturnNode({ value })], - sourceMap: value.sourceMap, - }))), + key('=').then(ExpressionBody), key('native'), diff --git a/src/validator.ts b/src/validator.ts index d5f91268..f88451a0 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -780,7 +780,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, sourceMap, level, values }) => ({ code, level, node, values, sourceMap: sourceMap ?? node.sourceMap } as Problem) ) ?? [], ...entries(validationsByKind(node)) .map(([code, validation]) => validation(node, code)!) .filter(result => result !== null), diff --git a/test/parser.test.ts b/test/parser.test.ts index 0f030688..5e05ca7d 100644 --- a/test/parser.test.ts +++ b/test/parser.test.ts @@ -1445,7 +1445,7 @@ describe('Wollok parser', () => { }) it('should parse annotated subnodes within expression bodies', () => { - 'method m() = @A(x = 1) 5'.should.be.parsedBy(parser).into( + 'method m() = (@A(x = 1) 5)'.should.be.parsedBy(parser).into( new Method({ name: 'm', body: new Body({ sentences: [ diff --git a/test/validator.test.ts b/test/validator.test.ts index fa25639a..6eb705e6 100644 --- a/test/validator.test.ts +++ b/test/validator.test.ts @@ -19,9 +19,9 @@ describe('Wollok Validations', () => { })) const environment = buildEnvironment(files) - const matchesExpectation = (problem: Problem, expected: Annotation) => { + const matchesExpectation = (problem: Problem, annotatedNode: Node, expected: Annotation) => { const code = expected.args.get('code')! - return problem.code === code && problem.sourceMap?.toString() === problem.node.sourceMap?.toString() + return problem.code === code && annotatedNode.sourceMap?.includes(problem.sourceMap!) } const errorLocation = (node: Node | Problem): string => `${node.sourceMap}` @@ -54,11 +54,11 @@ describe('Wollok Validations', () => { if (!code) fail('Missing required "code" argument in @Expect annotation') - const errors = problems.filter(problem => !matchesExpectation(problem, expectedProblem)) + const errors = problems.filter(problem => !matchesExpectation(problem, node, expectedProblem)) if (notEmpty(errors)) fail(`File contains errors: ${errors.map((_error) => _error.code + ' at ' + errorLocation(_error)).join(', ')}`) - const effectiveProblem = problems.find(problem => matchesExpectation(problem, expectedProblem)) + const effectiveProblem = problems.find(problem => matchesExpectation(problem, node, expectedProblem)) if (!effectiveProblem) fail(`Missing expected ${code} ${level ?? 'problem'} at ${errorLocation(node)}`) @@ -74,7 +74,7 @@ describe('Wollok Validations', () => { } for (const problem of problems) { - if (!expectedProblems.some(expectedProblem => matchesExpectation(problem, expectedProblem))) + if (!expectedProblems.some(expectedProblem => matchesExpectation(problem, node, expectedProblem))) fail(`Unexpected ${problem.code} ${problem.level} at ${errorLocation(node)}`) } })