diff --git a/gulpfile.babel.js b/gulpfile.babel.js index a2ff77d..df51510 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -160,6 +160,9 @@ const uglifyConfig = { output: { comments: /copyright/i, }, + compress: { + inline: 1, + }, }; gulp.task('build:js', () => diff --git a/src/parser/WDL/entities/Context.js b/src/parser/WDL/entities/Context.js index 4b3fe1c..90f8dde 100644 --- a/src/parser/WDL/entities/Context.js +++ b/src/parser/WDL/entities/Context.js @@ -136,7 +136,7 @@ export default class Context { .map((wfNode) => { const workflow = new Workflow(wfNode.attributes.name.source_string, { ast: _.cloneDeep(wfNode) }); workflow.i = Context.getInputsWorkflow(_.cloneDeep(wfNode.attributes.body)); - workflow.o = Context.getOutputsWorkflow(_.cloneDeep(wfNode.attributes)); + workflow.o = Context.getOutputsWorkflow(_.cloneDeep(wfNode.attributes.body)); return workflow; }); @@ -154,11 +154,11 @@ export default class Context { /** * Get all workflow inputs - * @param {ast} ast - ast tree node + * @param {ast} wfNodeBody - ast tree node.attributes.body */ - static getInputsWorkflow(ast) { + static getInputsWorkflow(wfNodeBody) { const inputs = {}; - ast.list.filter(item => item.name.toLowerCase() === Constants.DECLARATION) + wfNodeBody.list.filter(item => item.name.toLowerCase() === Constants.DECLARATION) .forEach((v) => { inputs[v.attributes.name.source_string] = { type: extractType(v.attributes.type), @@ -175,21 +175,47 @@ export default class Context { /** * Get all workflow outputs - * @param {ast} ast - ast tree node + * @param {ast} wfNodeBody - ast tree node.attributes.body */ - static getOutputsWorkflow(ast) { + static getOutputsWorkflow(wfNodeBody) { const outputs = {}; - ast.body.list.filter(item => item.name.toLowerCase() === Constants.WF_OUTPUTS) + wfNodeBody.list.filter(item => item.name.toLowerCase() === Constants.WF_OUTPUTS) .forEach((workflowoutputs) => { - workflowoutputs.attributes.outputs.list.forEach((v) => { - const node = v.attributes; - outputs[node.name.source_string] = { - type: extractType(node.type), - default: extractExpression(node.expression).string, - }; + workflowoutputs.attributes.outputs.list.forEach((wfOutput) => { + const node = wfOutput.attributes; + Context.proceedExpression(node, outputs); + Context.proceedWildcard(node, outputs); }); }); + return outputs; } + + static proceedWildcard(node, outputs) { + if (!node.fqn) { + return; + } + + const outputString = node.wildcard + ? `${node.fqn.source_string}.${node.wildcard.source_string}` + : node.fqn.source_string; + + outputs[outputString] = { + type: '', + multi: !!node.wildcard, + default: outputString, + }; + } + + static proceedExpression(node, outputs) { + if (!node.name && !node.type && !node.expression) { + return; + } + + outputs[node.name.source_string] = { + type: extractType(node.type), + default: extractExpression(node.expression).string, + }; + } } diff --git a/src/parser/WDL/entities/WDLWorkflow.js b/src/parser/WDL/entities/WDLWorkflow.js index ad09e9f..3f503bf 100644 --- a/src/parser/WDL/entities/WDLWorkflow.js +++ b/src/parser/WDL/entities/WDLWorkflow.js @@ -237,10 +237,10 @@ export default class WDLWorkflow { /** * Build the expressioned outputs - * @param {list} outputList - Array of ast nodes representing each output + * @param {[]} outputList - Array of ast nodes representing each output */ processExpressions(outputList) { - return outputList.forEach((item) => { + outputList.forEach((item) => { if (!item.name && !item.type && !item.expression) { return; } @@ -288,16 +288,16 @@ export default class WDLWorkflow { /** * Build the wildcard outputs - * @param {list} outputList - Array of ast nodes representing each output + * @param {[]} outputList - Array of ast nodes representing each output */ processWilds(outputList) { outputList.forEach((item) => { - if (!item.fqn && !item.wildcard) { + if (!item.fqn) { return; } const fqn = item.fqn; const wildcard = item.wildcard; - const res = ((fqn ? fqn.source_string : '') + (wildcard ? `.${wildcard.source_string}` : '')).trim(); + const res = (fqn.source_string + (wildcard ? `.${wildcard.source_string}` : '')).trim(); const obj = {}; obj[res] = { @@ -307,6 +307,49 @@ export default class WDLWorkflow { this.workflowStep.action.addPorts({ o: obj, }); + // WF output connections + if (!wildcard) { // syntax: call_name.output_name + const callOutput = fqn.source_string.split('.'); + if (callOutput.length < 2) { + return; + } + const callName = callOutput[0]; + const outputName = callOutput[1]; + const startStep = WDLWorkflow.findStepInStructureRecursively(this.workflowStep, callName); + + if (startStep) { + if (startStep.o[outputName]) { + this.workflowStep.o[fqn.source_string].bind(startStep.o[outputName]); + } else { + throw new WDLParserError( + `In '${this.workflowStep.name}' + output block undeclared variable is referenced: '${callName}.${outputName}'`); + } + } else { + throw new WDLParserError( + `In '${this.workflowStep.name}' + output block undeclared call is referenced: '${callName}'`); + } + } else { // syntax: call_name.* (all call's outputs) + const callName = fqn.source_string; + const startStep = WDLWorkflow.findStepInStructureRecursively(this.workflowStep, callName); + + if (startStep) { + if (_.size(startStep.o)) { + _.forEach(startStep.o, (output, outputName) => { + this.workflowStep.o[`${fqn.source_string}.*`].bind(startStep.o[outputName]); + }); + } else { + throw new WDLParserError( + `In '${this.workflowStep.name}' + output block undeclared variable is referenced: '${callName}.* (${callName} doesn't have any outputs)`); + } + } else { + throw new WDLParserError( + `In '${this.workflowStep.name}' + output block undeclared call is referenced: '${callName}'`); + } + } }); } diff --git a/test/parser/WDL/entities/ContextTest.js b/test/parser/WDL/entities/ContextTest.js index 0bc1e67..b3592ac 100644 --- a/test/parser/WDL/entities/ContextTest.js +++ b/test/parser/WDL/entities/ContextTest.js @@ -113,64 +113,55 @@ describe('parser/WDL/entities/Context', () => { const outputValueLhs = 'task1'; const outputValueRhs = 'rawVCF'; const partAst = { - name: { - id: 39, - str: 'identifier', - source_string: 'workflow123', - line: 1, - col: 10, - }, - body: { - list: [ - { - name: 'WorkflowOutputs', - attributes: { - outputs: { - list: [ - { - name: 'WorkflowOutputDeclaration', - attributes: { - type: { - id: 41, - str: 'type', - source_string: outputType, - line: 12, - col: 5, - }, - name: { - id: 39, - str: 'identifier', - source_string: outputName, - line: 12, - col: 10, - }, - expression: { - name: 'MemberAccess', - attributes: { - lhs: { - id: 39, - str: 'identifier', - source_string: outputValueLhs, - line: 12, - col: 19, - }, - rhs: { - id: 39, - str: 'identifier', - source_string: outputValueRhs, - line: 12, - col: 25, - }, + list: [ + { + name: 'WorkflowOutputs', + attributes: { + outputs: { + list: [ + { + name: 'WorkflowOutputDeclaration', + attributes: { + type: { + id: 41, + str: 'type', + source_string: outputType, + line: 12, + col: 5, + }, + name: { + id: 39, + str: 'identifier', + source_string: outputName, + line: 12, + col: 10, + }, + expression: { + name: 'MemberAccess', + attributes: { + lhs: { + id: 39, + str: 'identifier', + source_string: outputValueLhs, + line: 12, + col: 19, + }, + rhs: { + id: 39, + str: 'identifier', + source_string: outputValueRhs, + line: 12, + col: 25, }, }, }, }, - ], - }, + }, + ], }, }, - ], - }, + }, + ], }; const outputsWorkflow = Context.getOutputsWorkflow(partAst); @@ -179,5 +170,64 @@ describe('parser/WDL/entities/Context', () => { expect(outputsWorkflow[outputName].type).to.be.equal(outputType); expect(outputsWorkflow[outputName].default).to.be.equal(`${outputValueLhs}.${outputValueRhs}`); }); + + it('returns outputs for workflow with wildcards', () => { + const outputWildcardName = 'foo'; + const outputNullWildcardName = 'bar.out'; + const partAst = { + list: [ + { + name: 'WorkflowOutputs', + attributes: { + outputs: { + list: [ + { + name: 'WorkflowOutputWildcard', + attributes: { + fqn: { + id: 11, + str: 'fqn', + source_string: outputWildcardName, + line: 7, + col: 6, + }, + wildcard: { + id: 15, + str: 'asterisk', + source_string: '*', + line: 7, + col: 10, + }, + }, + }, + { + name: 'WorkflowOutputWildcard', + attributes: { + fqn: { + id: 11, + str: 'fqn', + source_string: outputNullWildcardName, + line: 7, + col: 5, + }, + wildcard: null, + }, + }, + ], + }, + }, + }, + ], + }; + + const outputsWorkflow = Context.getOutputsWorkflow(partAst); + + expect(outputsWorkflow[`${outputWildcardName}.*`]).to.be.not.empty; + expect(outputsWorkflow[outputNullWildcardName]).to.be.not.empty; + expect(outputsWorkflow[`${outputWildcardName}.*`].type).to.be.empty; + expect(outputsWorkflow[outputNullWildcardName].type).to.be.empty; + expect(outputsWorkflow[`${outputWildcardName}.*`].default).to.be.equal(`${outputWildcardName}.*`); + expect(outputsWorkflow[outputNullWildcardName].default).to.be.equal(outputNullWildcardName); + }); }); }); diff --git a/test/parser/WDL/entities/WDLWorkflowTest.js b/test/parser/WDL/entities/WDLWorkflowTest.js index b8d5f39..36750d2 100644 --- a/test/parser/WDL/entities/WDLWorkflowTest.js +++ b/test/parser/WDL/entities/WDLWorkflowTest.js @@ -971,6 +971,478 @@ describe('parser/WDL/entities/WDLWorkflow', () => { expect(() => new WDLWorkflow(ast, context)).to.throws(WDLParserError); }); + it('throws error when undeclared variable is referenced in workflow outputs with wildcard1', () => { + const ast = { + name: { + id: 14, + str: 'identifier', + source_string: 'foo', + line: 2, + col: 10, + }, + body: { + list: [ + { + name: 'Declaration', + attributes: { + type: { + id: 43, + str: 'type', + source_string: 'Int', + line: 3, + col: 3, + }, + name: { + id: 14, + str: 'identifier', + source_string: 'a', + line: 3, + col: 7, + }, + expression: { + id: 2, + str: 'integer', + source_string: '5', + line: 3, + col: 11, + }, + }, + }, + { + name: 'Declaration', + attributes: { + type: { + id: 43, + str: 'type', + source_string: 'Int', + line: 4, + col: 3, + }, + name: { + id: 14, + str: 'identifier', + source_string: 'b', + line: 4, + col: 7, + }, + expression: { + name: 'Add', + attributes: { + lhs: { + id: 2, + str: 'integer', + source_string: '5', + line: 4, + col: 11, + }, + rhs: { + id: 14, + str: 'identifier', + source_string: 'a', + line: 4, + col: 15, + }, + }, + }, + }, + }, + { + name: 'Call', + attributes: { + task: { + id: 11, + str: 'fqn', + source_string: 'bar', + line: 5, + col: 8, + }, + alias: null, + body: null, + }, + }, + { + name: 'WorkflowOutputs', + attributes: { + outputs: { + list: [ + { + name: 'WorkflowOutputWildcard', + attributes: { + fqn: { + id: 11, + str: 'fqn', + source_string: 'bar.out', + line: 7, + col: 5, + }, + wildcard: null, + }, + }, + ], + }, + }, + }, + ], + }, + }; + + expect(() => new WDLWorkflow(ast, { + actionMap: { + bar: { + }, + }, + })).to.throws(WDLParserError); + }); + + it('throws error when undeclared call is referenced in workflow outputs with wildcard1', () => { + const ast = { + name: { + id: 14, + str: 'identifier', + source_string: 'foo', + line: 2, + col: 10, + }, + body: { + list: [ + { + name: 'Declaration', + attributes: { + type: { + id: 43, + str: 'type', + source_string: 'Int', + line: 3, + col: 3, + }, + name: { + id: 14, + str: 'identifier', + source_string: 'a', + line: 3, + col: 7, + }, + expression: { + id: 2, + str: 'integer', + source_string: '5', + line: 3, + col: 11, + }, + }, + }, + { + name: 'Declaration', + attributes: { + type: { + id: 43, + str: 'type', + source_string: 'Int', + line: 4, + col: 3, + }, + name: { + id: 14, + str: 'identifier', + source_string: 'b', + line: 4, + col: 7, + }, + expression: { + name: 'Add', + attributes: { + lhs: { + id: 2, + str: 'integer', + source_string: '5', + line: 4, + col: 11, + }, + rhs: { + id: 14, + str: 'identifier', + source_string: 'a', + line: 4, + col: 15, + }, + }, + }, + }, + }, + { + name: 'WorkflowOutputs', + attributes: { + outputs: { + list: [ + { + name: 'WorkflowOutputWildcard', + attributes: { + fqn: { + id: 11, + str: 'fqn', + source_string: 'bar.out', + line: 7, + col: 5, + }, + wildcard: null, + }, + }, + ], + }, + }, + }, + ], + }, + }; + + expect(() => new WDLWorkflow(ast, { + actionMap: { + }, + })).to.throws(WDLParserError); + }); + + it('throws error when undeclared variable is referenced in workflow outputs with wildcard2', () => { + const ast = { + name: { + id: 14, + str: 'identifier', + source_string: 'foo', + line: 2, + col: 10, + }, + body: { + list: [ + { + name: 'Declaration', + attributes: { + type: { + id: 43, + str: 'type', + source_string: 'Int', + line: 3, + col: 3, + }, + name: { + id: 14, + str: 'identifier', + source_string: 'a', + line: 3, + col: 7, + }, + expression: { + id: 2, + str: 'integer', + source_string: '5', + line: 3, + col: 11, + }, + }, + }, + { + name: 'Declaration', + attributes: { + type: { + id: 43, + str: 'type', + source_string: 'Int', + line: 4, + col: 3, + }, + name: { + id: 14, + str: 'identifier', + source_string: 'b', + line: 4, + col: 7, + }, + expression: { + name: 'Add', + attributes: { + lhs: { + id: 2, + str: 'integer', + source_string: '5', + line: 4, + col: 11, + }, + rhs: { + id: 14, + str: 'identifier', + source_string: 'a', + line: 4, + col: 15, + }, + }, + }, + }, + }, + { + name: 'Call', + attributes: { + task: { + id: 11, + str: 'fqn', + source_string: 'bar', + line: 5, + col: 8, + }, + alias: null, + body: null, + }, + }, + { + name: 'WorkflowOutputs', + attributes: { + outputs: { + list: [ + { + name: 'WorkflowOutputWildcard', + attributes: { + fqn: { + id: 11, + str: 'fqn', + source_string: 'bar', + line: 7, + col: 6, + }, + wildcard: { + id: 15, + str: 'asterisk', + source_string: '*', + line: 7, + col: 10, + }, + }, + }, + ], + }, + }, + }, + ], + }, + }; + + expect(() => new WDLWorkflow(ast, { + actionMap: { + bar: { + }, + }, + })).to.throws(WDLParserError); + }); + + it('throws error when undeclared call is referenced in workflow outputs with wildcard2', () => { + const ast = { + name: { + id: 14, + str: 'identifier', + source_string: 'foo', + line: 2, + col: 10, + }, + body: { + list: [ + { + name: 'Declaration', + attributes: { + type: { + id: 43, + str: 'type', + source_string: 'Int', + line: 3, + col: 3, + }, + name: { + id: 14, + str: 'identifier', + source_string: 'a', + line: 3, + col: 7, + }, + expression: { + id: 2, + str: 'integer', + source_string: '5', + line: 3, + col: 11, + }, + }, + }, + { + name: 'Declaration', + attributes: { + type: { + id: 43, + str: 'type', + source_string: 'Int', + line: 4, + col: 3, + }, + name: { + id: 14, + str: 'identifier', + source_string: 'b', + line: 4, + col: 7, + }, + expression: { + name: 'Add', + attributes: { + lhs: { + id: 2, + str: 'integer', + source_string: '5', + line: 4, + col: 11, + }, + rhs: { + id: 14, + str: 'identifier', + source_string: 'a', + line: 4, + col: 15, + }, + }, + }, + }, + }, + { + name: 'WorkflowOutputs', + attributes: { + outputs: { + list: [ + { + name: 'WorkflowOutputWildcard', + attributes: { + fqn: { + id: 11, + str: 'fqn', + source_string: 'bar', + line: 7, + col: 6, + }, + wildcard: { + id: 15, + str: 'asterisk', + source_string: '*', + line: 7, + col: 10, + }, + }, + }, + ], + }, + }, + }, + ], + }, + }; + + expect(() => new WDLWorkflow(ast, { + actionMap: { + }, + })).to.throws(WDLParserError); + }); + it('supports workflow outputs with wildcard1', () => { const ast = { name: { @@ -1089,6 +1561,10 @@ describe('parser/WDL/entities/WDLWorkflow', () => { const workflow = new WDLWorkflow(ast, { actionMap: { bar: { + i: {}, + o: { + out: new Port('out'), + }, }, }, }); @@ -1220,6 +1696,10 @@ describe('parser/WDL/entities/WDLWorkflow', () => { const workflow = new WDLWorkflow(ast, { actionMap: { bar: { + i: {}, + o: { + out: new Port('out'), + }, }, }, });