From 0545a0d4c887c50210a3b6c08d5b1bb90d007c34 Mon Sep 17 00:00:00 2001 From: peze Date: Thu, 23 Nov 2023 14:46:58 +0800 Subject: [PATCH] add exception --- lib/parser.js | 131 +++++- lib/semantic.js | 72 ++- test/fixtures/extend_model/Darafile | 5 + test/fixtures/extend_model/main.dara | 40 ++ test/fixtures/extend_model/oss.dara | 15 + test/parser.test.js | 627 ++++++++++++++++++++++++++- test/semantic.test.js | 167 ++++++- 7 files changed, 1032 insertions(+), 25 deletions(-) create mode 100644 test/fixtures/extend_model/Darafile create mode 100644 test/fixtures/extend_model/main.dara create mode 100644 test/fixtures/extend_model/oss.dara diff --git a/lib/parser.js b/lib/parser.js index e2f3bf6..04672a1 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -131,7 +131,7 @@ class Parser extends BaseParser { } moduleBody() { - // moduleBody = "{" { const | type | model | api | function } "}" + // moduleBody = "{" { const | type | model | exception | api | function } "}" const nodes = []; while (this.look.tag) { let node; @@ -147,6 +147,8 @@ class Parser extends BaseParser { node = this.typedef(); } else if (this.isWord(Tag.ID, 'model')) { node = this.model(); + } else if (this.isWord(Tag.ID, 'exception')) { + node = this.exception(); } else if (this.isWord(Tag.ID, 'enum')) { node = this.enum(); } else if (this.isWord(Tag.ID, 'api')) { @@ -587,6 +589,119 @@ class Parser extends BaseParser { }; } + exception() { + // exception = "exception" exceptionName exceptionBody + const begin = this.getIndex(); + this.matchWord(Tag.ID, 'exception'); + const exceptionName = this.look; + this.match(Tag.ID); + let extendOn; + if (this.is(Tag.EXTENDS)) { + extendOn = this.extends(); + } + // 可选的 = + if (this.look.tag === '=') { + this.move(); + } + + const body = this.exceptionBody(); + let end = body.tokenRange[1]; + if (this.is(';')) { + // 可选的 ; + end = this.getIndex(); + this.move(); + } + + return { + type: 'exception', + exceptionName: exceptionName, + extendOn: extendOn, + exceptionBody: body, + tokenRange: [begin, end] + }; + } + + exceptionBody() { + // exceptionBody = "{" [ exceptionFields ] "}" + const begin = this.getIndex(); + this.match('{'); + + const nodes = []; + + if (this.is('}')) { + const end = this.getIndex(); + this.move(); + return { + type: 'exceptionBody', + nodes: nodes, + tokenRange: [begin, end] + }; + } + + var node = this.exceptionField(); + nodes.push(node); + + while (!this.is('}')) { + if (this.is(',')) { + this.move(); + + if (this.is('}')) { + // only one fields + break; + } + + let node = this.exceptionField(); + nodes.push(node); + } else { + this.error('expect ","'); + } + } + + const end = this.getIndex(); + this.match('}'); + + return { + type: 'exceptionBody', + nodes: nodes, + tokenRange: [begin, end] + }; + } + + + exceptionField() { + var required = true; + let fieldName = this.look; + const begin = this.getIndex(); + if (this.is(Tag.ID)) { + this.move(); + } else if ( + this.isID()) { + fieldName.tag = Tag.ID; + this.move(); + } else { + this.error(`only id is allowed`); + } + + if (this.look.tag === '?') { + required = false; + this.move(); + } + + this.match(':'); + const fieldValue = this.fieldValue(); + + const attrs = this.attrs(); + const end = this.getIndex(); + return { + type: 'exceptionField', + fieldName: fieldName, + required: required, + fieldValue: fieldValue, + attrs: attrs, + tokenRange: [begin, end] + }; + } + fieldValue() { // fieldValue = ( type | arrayType | modelBody | mapType ) // attrs = "(" attr { "," attr } ")" @@ -1836,8 +1951,18 @@ class Parser extends BaseParser { throwStmt() { const begin = this.getIndex(); this.match(Tag.THROW); - let expr = this.object(); - let end = expr.tokenRange[1]; + let expr, end; + if(this.look.tag === '{'){ + expr = this.object(); + end = expr.tokenRange[1]; + } else if(this.look.tag === Tag.NEW) { + expr = this.construct(); + end = expr.object.tokenRange[1]; + } else { + this.error('Unexpected expr: expect a exception or object.'); + } + + if (this.look.tag === ';') { end = this.getIndex(); this.move(); diff --git a/lib/semantic.js b/lib/semantic.js index 67dd689..5ad386f 100644 --- a/lib/semantic.js +++ b/lib/semantic.js @@ -394,12 +394,6 @@ function isNeedToMap(expect, actual) { return true; } -function findProperty(model, propName) { - return model.modelBody.nodes.find((item) => { - return item.fieldName.lexeme === propName; - }); -} - class TypeChecker { constructor(source, filename, root, libraries, inner = false) { this.source = source; @@ -449,15 +443,40 @@ class TypeChecker { throw new SyntaxError(message); } + findProperty(model, propName) { + const find = model.modelBody.nodes.find((item) => { + return item.fieldName.lexeme === propName; + }); + if(find || !model.extendOn) { + return find; + } + let extendModel; + if (model.extendOn.type === 'moduleModel') { + const [ main, ...path ] = model.extendOn.path; + const checker = this.dependencies.get(main.lexeme); + const typeName = path.map((item) => { + return item.lexeme; + }).join('.'); + extendModel = checker.models.get(typeName); + } else if (model.extendOn.idType === 'builtin_model') { + extendModel = builtin.get(model.extendOn.lexeme); + + } else { + extendModel = this.models.get(model.extendOn.lexeme); + } + return this.findProperty(extendModel, propName); + } + checkModels(ast) { const models = ast.moduleBody.nodes.filter((item) => { - return item.type === 'model'; + return item.type === 'model' || item.type === 'exception'; }); models.forEach((node) => { - const key = node.modelName.lexeme; + const name = node.type === 'model' ? node.modelName : node.exceptionName; + const key = name.lexeme; // 重复定义检查 if (this.models.has(key)) { - this.error(`redefined model "${key}"`, node.modelName); + this.error(`redefined model or exception "${key}"`, name); } this.models.set(key, node); }); @@ -467,7 +486,14 @@ class TypeChecker { if(node.extendOn) { this.checkType(node.extendOn); } - this.visitModel(node); + if(node.type === 'model') { + this.visitModel(node); + } else if(node.type === 'exception') { + this.visitException(node); + } else { + this.error('unimplement'); + } + }); } @@ -1581,7 +1607,7 @@ class TypeChecker { for (let i = 0; i < ast.propertyPath.length; i++) { let prop = ast.propertyPath[i]; currentPath.push(prop.lexeme); - let find = findProperty(current, prop.lexeme); + let find = this.findProperty(current, prop.lexeme); if (!find) { this.error(`The model ${currentPath.join('.')} is undefined`, prop); } @@ -1801,7 +1827,7 @@ class TypeChecker { for (let i = 0; i < ast.object.fields.length; i++) { const field = ast.object.fields[i]; const name = field.fieldName.lexeme; - const modelField = findProperty(model, name); + const modelField = this.findProperty(model, name); if (!modelField) { this.error(`the property "${name}" is undefined in model "${aliasId}.${modelName}"`, field.fieldName); } @@ -2111,7 +2137,7 @@ class TypeChecker { model = this.models.get(type.name); } - const find = findProperty(model, propName); + const find = this.findProperty(model, propName); if (!find) { return; } @@ -2649,7 +2675,14 @@ class TypeChecker { } visitThrow(ast, env) { - this.visitObject(ast.expr, env); + if(ast.expr.type === 'object') { + this.visitObject(ast.expr, env); + } else if(ast.expr.type === 'construct_model') { + this.visitConstructModel(ast.expr, env); + } else { + this.error('unimplement', ast); + } + } visitIf(ast, env) { @@ -2687,7 +2720,7 @@ class TypeChecker { } } - flatModel(root, modelBody, modelName) { + flatModel(root, modelBody, modelName, extendOn) { const keys = new Map(); for (var i = 0; i < modelBody.nodes.length; i++) { const node = modelBody.nodes[i]; @@ -2740,6 +2773,7 @@ class TypeChecker { this.models.set(modelName, { type: 'model', + extendOn: extendOn, modelName: { tag: Tag.ID, lexeme: modelName @@ -2752,7 +2786,13 @@ class TypeChecker { visitModel(ast) { assert.equal(ast.type, 'model'); const modelName = ast.modelName.lexeme; - this.flatModel(ast, ast.modelBody, modelName); + this.flatModel(ast, ast.modelBody, modelName, ast.extendOn); + } + + visitException(ast) { + assert.equal(ast.type, 'exception'); + const exceptionName = ast.exceptionName.lexeme; + this.flatModel(ast, ast.exceptionBody, exceptionName, ast.extendOn); } visitEnum(ast) { diff --git a/test/fixtures/extend_model/Darafile b/test/fixtures/extend_model/Darafile new file mode 100644 index 0000000..7e4f9a5 --- /dev/null +++ b/test/fixtures/extend_model/Darafile @@ -0,0 +1,5 @@ +{ + "libraries": { + "OSS": "./oss.dara" + } +} diff --git a/test/fixtures/extend_model/main.dara b/test/fixtures/extend_model/main.dara new file mode 100644 index 0000000..d16ff17 --- /dev/null +++ b/test/fixtures/extend_model/main.dara @@ -0,0 +1,40 @@ +import OSS + +model mainFile extends OSS.File { + size: number, +} + +exception MainFileError extends OSS.File { + size: number, +} + +exception ExtendFileError extends OSS.FileError { + size: number, +} + +exception ExtendSubFileError extends OSS.SubFile.file { + size: number, +} + +static function call(size: number): void { + var file = new mainFile{ + name = 'name', + size = 100, + }; + if(size > file.size) { + throw new MainFileError{ + name = 'name', + size = 100, + } + } else if (size == file.size){ + throw new ExtendFileError{ + code = 'name', + size = 100, + } + } else { + throw new ExtendSubFileError{ + name = 'name', + size = 100, + } + } +} diff --git a/test/fixtures/extend_model/oss.dara b/test/fixtures/extend_model/oss.dara new file mode 100644 index 0000000..7d76f07 --- /dev/null +++ b/test/fixtures/extend_model/oss.dara @@ -0,0 +1,15 @@ + +model File = { + name: string, +}; + +exception FileError = { + code: string, +} + +model SubFile = { + file: { + name: string, + } +} + diff --git a/test/parser.test.js b/test/parser.test.js index d0e11b0..b590953 100644 --- a/test/parser.test.js +++ b/test/parser.test.js @@ -1396,7 +1396,7 @@ describe('parser', function () { stmts('throw'); }).to.throwException(function (e) { // get the exception object expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect {, but }`); + expect(e.message).to.be(`Unexpected token: }. Unexpected expr: expect a exception or object.`); }); expect(() => { @@ -4026,7 +4026,6 @@ describe('parser', function () { const [fun] = ast.moduleBody.nodes; expect(fun.type).to.be('function'); expect(fun.isAsync).to.be(true); - // console.log('%j', fun.returnType); expect(fun.returnType).to.eql({ 'loc': loc(2, 31, 2, 46), 'type': 'iterator', @@ -7980,7 +7979,6 @@ describe('parser', function () { `, '__filename'); }).to.throwException(function (e) { // get the exception object expect(e).to.be.a(SyntaxError); - console.log(e.message); expect(e.message).to.be('Unexpected token: Word: `true`. expect string or number'); }); }); @@ -7994,7 +7992,6 @@ describe('parser', function () { `, '__filename'); }).to.throwException(function (e) { // get the exception object expect(e).to.be.a(SyntaxError); - console.log(e.message); expect(e.message).to.be('Unexpected token: ,. Expect (, but ,'); }); }); @@ -8503,4 +8500,626 @@ describe('parser', function () { }); }); + + + it('exception field should be ok', function () { + function exceptionField(value) { + var ast = parse(` + exception id = { + ${value} + } + `, '__filename'); + return ast.moduleBody.nodes[0].exceptionBody; + } + + expect(() => { + exceptionField('?'); + }).to.throwException(function (e) { // get the exception object + expect(e).to.be.a(SyntaxError); + expect(e.message).to.be(`Unexpected token: ?. only id is allowed`); + }); + + expect(() => { + exceptionField(`name`); + }).to.throwException(function (e) { // get the exception object + expect(e).to.be.a(SyntaxError); + expect(e.message).to.be(`Unexpected token: }. Expect :, but }`); + }); + + expect(() => { + exceptionField(`name?`); + }).to.throwException(function (e) { // get the exception object + expect(e).to.be.a(SyntaxError); + expect(e.message).to.be(`Unexpected token: }. Expect :, but }`); + }); + + expect(() => { + exceptionField(`name?:`); + }).to.throwException(function (e) { // get the exception object + expect(e).to.be.a(SyntaxError); + expect(e.message).to.be(`Unexpected token: }. expect "{", "[", "string", "number", "map", ID`); + }); + + expect(exceptionField(`name?: string`)).to.be.eql({ + 'nodes': [ + { + 'attrs': [], + 'fieldName': { + 'index': 5, + 'lexeme': 'name', + 'tag': 2, + loc: loc(3, 11, 3, 15) + }, + 'fieldValue': { + 'fieldType': 'string', + 'type': 'fieldType' + }, + 'required': false, + 'tokenRange': [5, 9], + 'type': 'exceptionField' + } + ], + 'tokenRange': [4, 9], + 'type': 'exceptionBody' + }); + + expect(exceptionField(`object?: string`)).to.be.eql({ + 'nodes': [ + { + 'attrs': [], + 'fieldName': { + 'index': 5, + 'lexeme': 'object', + 'tag': 2, + loc: loc(3, 11, 3, 17) + }, + 'fieldValue': { + 'fieldType': 'string', + 'type': 'fieldType' + }, + 'required': false, + 'tokenRange': [5, 9], + 'type': 'exceptionField' + } + ], + 'tokenRange': [4, 9], + 'type': 'exceptionBody' + }); + + expect(exceptionField(`new?: string`)).to.be.eql({ + 'nodes': [ + { + 'attrs': [], + 'fieldName': { + 'index': 5, + 'lexeme': 'new', + 'tag': 2, + loc: loc(3, 11, 3, 14) + }, + 'fieldValue': { + 'fieldType': 'string', + 'type': 'fieldType' + }, + 'required': false, + 'tokenRange': [5, 9], + 'type': 'exceptionField' + } + ], + 'tokenRange': [4, 9], + 'type': 'exceptionBody' + }); + + expect(exceptionField(`rpc?: string`)).to.be.eql({ + 'nodes': [ + { + 'attrs': [], + 'fieldName': { + 'index': 5, + 'lexeme': 'rpc', + 'tag': 2, + loc: loc(3, 11, 3, 14) + }, + 'fieldValue': { + 'fieldType': 'string', + 'type': 'fieldType' + }, + 'required': false, + 'tokenRange': [5, 9], + 'type': 'exceptionField' + } + ], + 'tokenRange': [4, 9], + 'type': 'exceptionBody' + }); + + expect(exceptionField(`super?: string`)).to.be.eql({ + 'nodes': [ + { + 'attrs': [], + 'fieldName': { + 'index': 5, + 'lexeme': 'super', + 'tag': 2, + loc: loc(3, 11, 3, 16) + }, + 'fieldValue': { + 'fieldType': 'string', + 'type': 'fieldType' + }, + 'required': false, + 'tokenRange': [5, 9], + 'type': 'exceptionField' + } + ], + 'tokenRange': [4, 9], + 'type': 'exceptionBody' + }); + + expect(exceptionField(`number?: string`)).to.be.eql({ + 'nodes': [ + { + 'attrs': [], + 'fieldName': { + 'index': 5, + 'lexeme': 'number', + 'tag': 2, + loc: loc(3, 11, 3, 17) + }, + 'fieldValue': { + 'fieldType': 'string', + 'type': 'fieldType' + }, + 'required': false, + 'tokenRange': [5, 9], + 'type': 'exceptionField' + } + ], + 'tokenRange': [4, 9], + 'type': 'exceptionBody' + }); + + expect(exceptionField(`name?: ID`)).to.be.eql({ + 'nodes': [ + { + 'attrs': [], + 'fieldName': { + 'index': 5, + 'lexeme': 'name', + 'tag': 2, + loc: loc(3, 11, 3, 15) + }, + 'fieldValue': { + 'fieldType': { + 'index': 8, + 'lexeme': 'ID', + 'tag': 2, + loc: loc(3, 18, 3, 20) + }, + 'type': 'fieldType' + }, + 'tokenRange': [5, 9], + 'required': false, + 'type': 'exceptionField' + } + ], + 'tokenRange': [4, 9], + 'type': 'exceptionBody' + }); + + expect(exceptionField(`name?: {}`)).to.be.eql({ + 'nodes': [ + { + 'attrs': [], + 'fieldName': { + 'index': 5, + 'lexeme': 'name', + 'tag': 2, + loc: loc(3, 11, 3, 15) + }, + 'fieldValue': { + 'nodes': [], + 'tokenRange': [8, 9], + 'type': 'modelBody' + }, + 'required': false, + 'tokenRange': [5, 10], + 'type': 'exceptionField' + } + ], + 'tokenRange': [4, 10], + 'type': 'exceptionBody' + }); + expect(() => { + exceptionField(`name?: [`); + }).to.throwException(function (e) { // get the exception object + expect(e).to.be.a(SyntaxError); + expect(e.message).to.be(`Unexpected token: }. expect type or model name`); + }); + + expect(() => { + exceptionField(`name?: [ {`); + }).to.throwException(function (e) { // get the exception object + expect(e).to.be.a(SyntaxError); + expect(e.message).to.be(`Unexpected token: EOF. Expect ], but EOF`); + }); + + expect(exceptionField(`name?: [{}]`)).to.be.eql({ + 'nodes': [ + { + 'attrs': [], + 'fieldName': { + 'index': 5, + 'lexeme': 'name', + 'tag': 2, + loc: loc(3, 11, 3, 15) + }, + 'fieldValue': { + 'fieldType': 'array', + 'fieldItemType': { + 'nodes': [], + 'tokenRange': [9, 10], + 'type': 'modelBody' + }, + 'type': 'fieldType' + }, + 'tokenRange': [5, 12], + 'required': false, + 'type': 'exceptionField' + } + ], + 'tokenRange': [4, 12], + 'type': 'exceptionBody' + }); + + expect(exceptionField(`name?: [ string ]`)).to.be.eql({ + 'nodes': [ + { + 'attrs': [], + 'fieldName': { + 'index': 5, + 'lexeme': 'name', + 'tag': 2, + loc: loc(3, 11, 3, 15) + }, + 'fieldValue': { + 'fieldType': 'array', + 'fieldItemType': { + 'index': 9, + 'lexeme': 'string', + 'tag': 8, + loc: loc(3, 20, 3, 26) + }, + 'type': 'fieldType' + }, + 'tokenRange': [5, 11], + 'required': false, + 'type': 'exceptionField' + } + ], + 'tokenRange': [4, 11], + 'type': 'exceptionBody' + }); + + expect(exceptionField(`name: string,`)).to.be.eql({ + 'nodes': [ + { + 'attrs': [], + 'fieldName': { + 'index': 5, + 'lexeme': 'name', + 'tag': 2, + loc: loc(3, 11, 3, 15) + }, + 'fieldValue': { + 'fieldType': 'string', + 'type': 'fieldType' + }, + 'required': true, + 'tokenRange': [5, 8], + 'type': 'exceptionField' + } + ], + 'tokenRange': [4, 9], + 'type': 'exceptionBody' + }); + + expect(exceptionField(`name: [ map[string]any ],`)).to.be.eql({ + 'nodes': [ + { + 'attrs': [], + 'fieldName': { + 'index': 5, + 'lexeme': 'name', + 'tag': 2, + loc: loc(3, 11, 3, 15) + }, + 'fieldValue': { + 'fieldItemType': { + 'keyType': { + 'index': 10, + 'lexeme': 'string', + 'loc': loc(3, 23, 3, 29), + 'tag': 8 + }, + 'type': 'map', + 'valueType': { + 'index': 12, + 'lexeme': 'any', + 'loc': loc(3, 30, 3, 33), + 'tag': 8 + }, + loc: loc(3, 19, 3, 33) + }, + 'fieldType': 'array', + 'type': 'fieldType' + }, + 'tokenRange': [5, 14], + 'required': true, + 'type': 'exceptionField' + } + ], + 'tokenRange': [4, 15], + 'type': 'exceptionBody' + }); + + expect(exceptionField(`name?: string, name2: string`)).to.be.eql({ + 'nodes': [ + { + 'attrs': [], + 'fieldName': { + 'lexeme': 'name', + 'index': 5, + 'tag': 2, + loc: loc(3, 11, 3, 15) + }, + 'fieldValue': { + 'fieldType': 'string', + 'type': 'fieldType' + }, + 'tokenRange': [5, 9], + 'required': false, + 'type': 'exceptionField' + }, + { + 'attrs': [], + 'fieldName': { + 'index': 10, + 'lexeme': 'name2', + 'tag': 2, + loc: loc(3, 26, 3, 31) + }, + 'fieldValue': { + 'fieldType': 'string', + 'type': 'fieldType' + }, + 'tokenRange': [10, 13], + 'required': true, + 'type': 'exceptionField' + } + ], + 'tokenRange': [4, 13], + 'type': 'exceptionBody' + }); + + expect(exceptionField(`name?: string, name2: string,`)).to.be.eql({ + 'nodes': [ + { + 'attrs': [], + 'fieldName': { + 'index': 5, + 'lexeme': 'name', + 'tag': 2, + loc: loc(3, 11, 3, 15) + }, + 'fieldValue': { + 'fieldType': 'string', + 'type': 'fieldType' + }, + 'tokenRange': [5, 9], + 'required': false, + 'type': 'exceptionField' + }, + { + 'attrs': [], + 'fieldName': { + 'index': 10, + 'lexeme': 'name2', + 'tag': 2, + loc: loc(3, 26, 3, 31) + }, + 'fieldValue': { + 'fieldType': 'string', + 'type': 'fieldType' + }, + 'tokenRange': [10, 13], + 'required': true, + 'type': 'exceptionField' + } + ], + 'tokenRange': [4, 14], + 'type': 'exceptionBody' + }); + + expect(() => { + exceptionField(`name: string {`); + }).to.throwException(function (e) { // get the exception object + expect(e).to.be.a(SyntaxError); + expect(e.message).to.be(`Unexpected token: {. expect ","`); + }); + }); + + it('exception filed attrs should be ok', function () { + function exceptionFieldAttrs(value) { + var ast = parse(` + exception id = { + name: string${value} + } + `, '__filename'); + return ast.moduleBody.nodes[0].exceptionBody.nodes[0].attrs; + } + + expect(() => { + exceptionFieldAttrs('('); + }).to.throwException(function (e) { // get the exception object + expect(e).to.be.a(SyntaxError); + expect(e.message).to.be(`Unexpected token: }. Expect ID, but }`); + }); + + expect(() => { + exceptionFieldAttrs('(id'); + }).to.throwException(function (e) { // get the exception object + expect(e).to.be.a(SyntaxError); + expect(e.message).to.be(`Unexpected token: }. Expect =, but }`); + }); + + expect(() => { + exceptionFieldAttrs('(id='); + }).to.throwException(function (e) { // get the exception object + expect(e).to.be.a(SyntaxError); + expect(e.message).to.be(`Unexpected token: }. expect string, number, bool`); + }); + + expect(() => { + exceptionFieldAttrs('(id="attr_value"'); + }).to.throwException(function (e) { // get the exception object + expect(e).to.be.a(SyntaxError); + expect(e.message).to.be(`Unexpected token: }. Expect ,, but }`); + }); + + expect(() => { + exceptionFieldAttrs('(id="attr_value",'); + }).to.throwException(function (e) { // get the exception object + expect(e).to.be.a(SyntaxError); + expect(e.message).to.be(`Unexpected token: }. Expect ID, but }`); + }); + + expect(exceptionFieldAttrs('(id="attr_value")')).to.be.eql([ + { + 'attrName': { + 'index': 9, + 'lexeme': 'id', + 'tag': 2, + loc: loc(3, 24, 3, 26) + }, + 'attrValue': { + 'index': 11, + 'string': 'attr_value', + 'tag': 1, + loc: loc(3, 28, 3, 38) + }, + 'type': 'attr' + } + ]); + + expect(exceptionFieldAttrs('(id="attr_value",id2="value2")')).to.be.eql([ + { + 'attrName': { + 'index': 9, + 'lexeme': 'id', + 'tag': 2, + loc: loc(3, 24, 3, 26) + }, + 'attrValue': { + 'index': 11, + 'string': 'attr_value', + 'tag': 1, + loc: loc(3, 28, 3, 38) + }, + 'type': 'attr' + }, + { + 'attrName': { + 'index': 13, + 'lexeme': 'id2', + 'tag': 2, + loc: loc(3, 40, 3, 43) + }, + 'attrValue': { + 'index': 15, + 'string': 'value2', + 'tag': 1, + loc: loc(3, 45, 3, 51) + }, + 'type': 'attr' + } + ]); + }); + + it('extends from exception/model should ok', function () { + let ast = parse(` + exception Base {}; + exception Derived extends Base {}; + `, '__filename'); + let derived = ast.moduleBody.nodes[1]; + expect(derived.extendOn).to.eql({ + 'index': 9, + 'lexeme': 'Base', + 'loc': loc(3, 31, 3, 35), + 'tokenRange': [8, 9], + 'tag': 2 + }); + + ast = parse(` + exception Base { + sub: {} + }; + exception Derived extends Base.sub {}; + `, '__filename'); + derived = ast.moduleBody.nodes[1]; + expect(derived.extendOn).to.eql({ + 'type': 'subModel_or_moduleModel', + 'path': [ + { + 'tag': 2, + 'loc': loc(5, 31, 5 ,35), + 'lexeme': 'Base', + 'index': 13 + }, + { + 'tag': 2, + 'loc': loc(5, 36, 5, 39), + 'lexeme': 'sub', + 'index': 15 + } + ] + }); + + ast = parse(` + model Base {}; + exception Derived extends Base {}; + `, '__filename'); + derived = ast.moduleBody.nodes[1]; + expect(derived.extendOn).to.eql({ + 'index': 9, + 'lexeme': 'Base', + 'loc': loc(3, 31, 3, 35), + 'tokenRange': [8, 9], + 'tag': 2 + }); + + ast = parse(` + model Base { + sub: {} + }; + exception Derived extends Base.sub {}; + `, '__filename'); + derived = ast.moduleBody.nodes[1]; + expect(derived.extendOn).to.eql({ + 'type': 'subModel_or_moduleModel', + 'path': [ + { + 'tag': 2, + 'loc': loc(5, 31, 5 ,35), + 'lexeme': 'Base', + 'index': 13 + }, + { + 'tag': 2, + 'loc': loc(5, 36, 5, 39), + 'lexeme': 'sub', + 'index': 15 + } + ] + }); + }); }); diff --git a/test/semantic.test.js b/test/semantic.test.js index 75602b2..570416c 100644 --- a/test/semantic.test.js +++ b/test/semantic.test.js @@ -145,6 +145,49 @@ describe('semantic', function () { }).to.not.throwException(); }); + it('use extends model field should not ok', function () { + expect(() => { + parse(` + model defined = { + value: number, + }; + + model modelname extends defined { + key: string + }; + + init() { + var m = new modelname { + key = 'key', + value = 100 + }; + } + `, '__filename'); + }).to.not.throwException(); + }); + + it('overwrite extends model field should not ok', function () { + expect(() => { + parse(` + model defined = { + value: number, + }; + + model modelname extends defined { + key: string, + value: string + }; + + init() { + var m = new modelname { + key = 'key', + value = 'value' + }; + } + `, '__filename'); + }).to.not.throwException(); + }); + it('redefine type should not ok', function () { expect(() => { parse(` @@ -163,7 +206,27 @@ describe('semantic', function () { model id = { key: string }`, '__filename'); }).to.throwException(function (e) { // get the exception object expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`redefined model "id"`); + expect(e.message).to.be(`redefined model or exception "id"`); + }); + }); + + it('redefine exception should not ok', function () { + expect(() => { + parse(` + exception id = { key: string } + exception id = { key: string }`, '__filename'); + }).to.throwException(function (e) { // get the exception object + expect(e).to.be.a(SyntaxError); + expect(e.message).to.be(`redefined model or exception "id"`); + }); + + expect(() => { + parse(` + model id = { key: string } + exception id = { key: string }`, '__filename'); + }).to.throwException(function (e) { // get the exception object + expect(e).to.be.a(SyntaxError); + expect(e.message).to.be(`redefined model or exception "id"`); }); }); @@ -576,6 +639,7 @@ describe('semantic', function () { const modelA = ast.models.a; expect(modelA).to.eql({ 'annotation': undefined, + 'extendOn': undefined, 'modelBody': { 'nodes': [ { @@ -635,6 +699,7 @@ describe('semantic', function () { const modelAB = ast.models['a.b']; expect(modelAB).to.eql({ 'annotation': undefined, + 'extendOn': undefined, 'modelBody': { 'nodes': [ { @@ -711,6 +776,7 @@ describe('semantic', function () { type: 'model', modelName: { tag: 2, lexeme: 'GroupDetailResponse.abilities' }, 'annotation': undefined, + 'extendOn': undefined, modelBody: { type: 'modelBody', 'tokenRange': [8, 21], @@ -781,6 +847,7 @@ describe('semantic', function () { const modelA = ast.models.a; expect(modelA).to.eql({ 'annotation': undefined, + 'extendOn': undefined, 'modelBody': { 'nodes': [ { @@ -840,6 +907,7 @@ describe('semantic', function () { const modelB = ast.models.b; expect(modelB).to.eql({ 'annotation': undefined, + 'extendOn': undefined, 'modelBody': { 'nodes': [ { @@ -920,6 +988,7 @@ describe('semantic', function () { const GroupDetailResponse = ast.models['GroupDetailResponse']; expect(GroupDetailResponse).to.be.eql({ 'annotation': undefined, + 'extendOn': undefined, 'modelBody': { 'nodes': [ { @@ -1306,7 +1375,6 @@ describe('semantic', function () { `, '__filename'); }).to.throwException(function (e) { expect(e).to.be.a(SyntaxError); - console.log(e.message); expect(e.message).to.be(`the return type is not expected, expect: string, actual: integer`); }); }); @@ -2732,6 +2800,94 @@ describe('semantic', function () { }`, '__filename'); }).to.not.throwError(); + + expect(function () { + parse(` + exception Error {}; + init() {} + api hello(): void { + throw new Error{}; + } returns { + + }`, '__filename'); + }).to.not.throwError(); + + expect(function () { + parse(` + exception Error { + status: number, + message: string, + code: string, + data: map[string]string + }; + init() {} + api hello(): void { + throw new Error{ + status = 200, + message = 'error message', + code = 'Error', + data = { + test = 'test', + } + }; + } returns { + + }`, '__filename'); + }).to.not.throwError(); + + expect(function () { + parse(` + model Base { + name: string, + }; + exception Error extends Base { + message: string, + code: string, + }; + init() {} + api hello(): void { + throw new Error{ + name = 'errorName', + message = 'error message', + code = 'extendError' + }; + } returns { + + }`, '__filename'); + }).to.not.throwError(); + + expect(function () { + parse(` + exception Error extends $Error {}; + init() {} + api hello(): void { + throw new Error{ + name = 'extendError', + message = 'error message', + code = 'extendError' + }; + } returns { + + }`, '__filename'); + }).to.not.throwError(); + + expect(function () { + parse(` + exception Error { + status: number, + }; + init() {} + api hello(): void { + throw new Error{ + statusCode = 200, + }; + } returns { + + }`, '__filename'); + }).to.throwError(function(e) { + expect(e).to.be.an(SyntaxError); + expect(e.message).to.be('the property "statusCode" is undefined in model "Error.Error"'); + }); }); describe('needToMap', function () { @@ -6724,4 +6880,11 @@ describe('semantic', function () { }); }); + + it('extend a module model/exception shoule be ok', function(){ + expect(function () { + readAndParse('fixtures/extend_model/main.dara'); + }).to.not.throwException(); + + }); });