Skip to content

Commit 79d4e1f

Browse files
committed
feat(core): add "main" denotation of req extension
add a new argument (the second one) of extension, its value should be "true" or "false", indicating whether this builder is the final builder or not.
1 parent c31f7bd commit 79d4e1f

File tree

3 files changed

+125
-26
lines changed

3 files changed

+125
-26
lines changed

packages/core/src/lib/template-engine/extensions/tags/req.ts

+61-24
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import * as nunjucks from 'nunjucks';
77
import { injectable, inject } from 'inversify';
88
import { TYPES } from '@vulcan/core/containers';
99

10+
const FINIAL_BUILDER_NAME = 'FINAL_BUILDER';
11+
1012
// TODO: temporary interface
1113
export interface QueryBuilder {
1214
count(): QueryBuilder;
@@ -29,37 +31,68 @@ export class ReqExtension implements NunjucksTagExtension {
2931

3032
public parse(
3133
parser: nunjucks.parser.Parser,
32-
nodes: typeof nunjucks.nodes
34+
nodes: typeof nunjucks.nodes,
35+
lexer: typeof nunjucks.lexer
3336
): NunjucksTagExtensionParseResult {
34-
// get the tag token
35-
const token = parser.nextToken();
36-
37-
const args = parser.parseSignature(null, true);
38-
parser.advanceAfterBlockEnd(token.value);
39-
40-
const requestQuery = parser.parseUntilBlocks('endreq');
41-
parser.advanceAfterBlockEnd();
37+
// {% req var (main) %} body {% endreq %}
38+
// consume req tag
39+
const reqToken = parser.nextToken();
40+
// variable
41+
let nextToken = parser.peekToken();
42+
if (nextToken.type === lexer.TOKEN_BLOCK_END) {
43+
// {% req %}
44+
parser.fail(`Expected a variable`, nextToken.lineno, nextToken.colno);
45+
}
46+
if (nextToken.type !== lexer.TOKEN_SYMBOL) {
47+
parser.fail(
48+
`Expected a symbol, but got ${nextToken.type}`,
49+
nextToken.lineno,
50+
nextToken.colno
51+
);
52+
}
53+
const variable = parser.parseExpression();
4254

43-
const variable = args.children[0];
44-
if (!variable) {
45-
parser.fail(`Expected a variable`, token.lineno, token.colno);
55+
// main denotation
56+
nextToken = parser.peekToken();
57+
let mainBuilder = false;
58+
if (nextToken.type !== lexer.TOKEN_BLOCK_END) {
59+
if (nextToken.type !== lexer.TOKEN_SYMBOL || nextToken.value !== 'main') {
60+
parser.fail(
61+
`Expected a symbol "main"`,
62+
nextToken.lineno,
63+
nextToken.colno
64+
);
65+
}
66+
mainBuilder = true;
67+
// Consume this token (main)
68+
parser.nextToken();
4669
}
47-
if (!(variable instanceof nodes.Symbol)) {
70+
71+
const endToken = parser.nextToken();
72+
if (endToken.type !== lexer.TOKEN_BLOCK_END) {
4873
parser.fail(
49-
`Expected a symbol, but got ${variable.typename}`,
50-
variable.lineno,
51-
variable.colno
74+
`Expected a block end, but got ${endToken.type}`,
75+
endToken.lineno,
76+
endToken.colno
5277
);
5378
}
5479

55-
const variableName = new nodes.Literal(
56-
variable.colno,
57-
variable.lineno,
58-
(variable as nunjucks.nodes.Symbol).value
59-
);
80+
const requestQuery = parser.parseUntilBlocks('endreq');
81+
parser.advanceAfterBlockEnd();
6082

61-
const argsNodeToPass = new nodes.NodeList(args.lineno, args.colno);
62-
argsNodeToPass.addChild(variableName);
83+
const argsNodeToPass = new nodes.NodeList(reqToken.lineno, reqToken.colno);
84+
// variable name
85+
argsNodeToPass.addChild(
86+
new nodes.Literal(
87+
variable.colno,
88+
variable.lineno,
89+
(variable as nunjucks.nodes.Symbol).value
90+
)
91+
);
92+
// is main builder
93+
argsNodeToPass.addChild(
94+
new nodes.Literal(variable.colno, variable.lineno, String(mainBuilder))
95+
);
6396

6497
return {
6598
argsNodeList: argsNodeToPass,
@@ -69,12 +102,16 @@ export class ReqExtension implements NunjucksTagExtension {
69102

70103
public async run({ context, args }: NunjucksTagExtensionRunOptions) {
71104
const name: string = args[0];
72-
const requestQuery: () => string = args[1];
105+
const requestQuery: () => string = args[2];
73106
const query = requestQuery()
74107
.split(/\r?\n/)
75108
.filter((line) => line.trim().length > 0)
76109
.join('\n');
77110
const builder = await this.executor.createBuilder(query);
78111
context.setVariable(name, builder);
112+
113+
if (Boolean(args[1])) {
114+
context.setVariable(FINIAL_BUILDER_NAME, builder);
115+
}
79116
}
80117
}

packages/core/test/template-engine/extensions/tags/req.spec.ts

+37-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ afterEach(() => {
3838
container.unbindAll();
3939
});
4040

41-
it('req extension should execute correct query and set variable', async () => {
41+
it.only('req extension should execute correct query and set variable', async () => {
4242
// Arrange
4343
const compiler = container.get<Compiler>(TYPES.Compiler);
4444
const loader = container.get<InMemoryCodeLoader>(TYPES.CompilerLoader);
@@ -72,7 +72,7 @@ it('if argument is not a symbol, extension should throw', async () => {
7272
select count(*) as count from user where user.id = '{{ params.userId }}';
7373
{% endreq %}
7474
`)
75-
).toThrow(`Expected a symbol, but got Literal`);
75+
).toThrow(`Expected a symbol, but got string`);
7676
});
7777

7878
it('if argument is missing, extension should throw', async () => {
@@ -88,3 +88,38 @@ select count(*) as count from user where user.id = '{{ params.userId }}';
8888
`)
8989
).toThrow(`Expected a variable`);
9090
});
91+
92+
it('if the main denotation is replaces other keywords than "main", extension should throw an error', async () => {
93+
// Arrange
94+
const compiler = container.get<Compiler>(TYPES.Compiler);
95+
96+
// Action, Assert
97+
expect(() =>
98+
compiler.compile(`
99+
{% req user super %}
100+
some statement
101+
{% endreq %}
102+
`)
103+
).toThrow(`Expected a symbol "main"`);
104+
});
105+
106+
it('the main denotation should be parsed into the second args node', async () => {
107+
// Arrange
108+
const compiler = container.get<NunjucksCompiler>(TYPES.Compiler);
109+
110+
// Action
111+
const { ast: astWithMainBuilder } = compiler.generateAst(
112+
`{% req user main %} some statement {% endreq %}`
113+
);
114+
const { ast: astWithoutMainBuilder } = compiler.generateAst(
115+
`{% req user %} some statement {% endreq %}`
116+
);
117+
118+
// Assert
119+
expect((astWithMainBuilder as any).children[0].args.children[1].value).toBe(
120+
'true'
121+
);
122+
expect(
123+
(astWithoutMainBuilder as any).children[0].args.children[1].value
124+
).toBe('false');
125+
});

types/nunjucks.d.ts

+27
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ declare module 'nunjucks' {
276276
parseUntilBlocks(...blockName: string[]): nodes.NodeList;
277277
advanceAfterBlockEnd(): Token;
278278
fail(message: string, lineno?: number, colno?: number): void;
279+
parseExpression(): Nodes;
279280
}
280281
}
281282

@@ -345,6 +346,32 @@ declare module 'nunjucks' {
345346

346347
namespace lexer {
347348
function lexer(src: string, opts: any): any;
349+
const TOKEN_STRING: string;
350+
const TOKEN_WHITESPACE: string;
351+
const TOKEN_DATA: string;
352+
const TOKEN_BLOCK_START: string;
353+
const TOKEN_BLOCK_END: string;
354+
const TOKEN_VARIABLE_START: string;
355+
const TOKEN_VARIABLE_END: string;
356+
const TOKEN_COMMENT: string;
357+
const TOKEN_LEFT_PAREN: string;
358+
const TOKEN_RIGHT_PAREN: string;
359+
const TOKEN_LEFT_BRACKET: string;
360+
const TOKEN_RIGHT_BRACKET: string;
361+
const TOKEN_LEFT_CURLY: string;
362+
const TOKEN_RIGHT_CURLY: string;
363+
const TOKEN_OPERATOR: string;
364+
const TOKEN_COMMA: string;
365+
const TOKEN_COLON: string;
366+
const TOKEN_TILDE: string;
367+
const TOKEN_PIPE: string;
368+
const TOKEN_INT: string;
369+
const TOKEN_FLOAT: string;
370+
const TOKEN_BOOLEAN: string;
371+
const TOKEN_NONE: string;
372+
const TOKEN_SYMBOL: string;
373+
const TOKEN_SPECIAL: string;
374+
const TOKEN_REGEX: string;
348375
}
349376

350377
interface Token {

0 commit comments

Comments
 (0)