Comparing version 2.2.0 to 3.0.0
export * from './src/Contracts'; | ||
import { Edge } from './src/Edge'; | ||
import { safeValue } from './src/Context'; | ||
import { GLOBALS } from './src/Edge/globals'; | ||
/** | ||
* Default export | ||
*/ | ||
declare const edge: Edge; | ||
export { Edge }; | ||
export default edge; | ||
export { safeValue, withCtx } from './src/Context'; | ||
export { Edge, safeValue, GLOBALS }; |
@@ -10,14 +10,14 @@ "use strict"; | ||
*/ | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Edge_1 = require("./src/Edge"); | ||
exports.Edge = Edge_1.Edge; | ||
const globals_1 = __importDefault(require("./src/Edge/globals")); | ||
const Context_1 = require("./src/Context"); | ||
exports.safeValue = Context_1.safeValue; | ||
const globals_1 = require("./src/Edge/globals"); | ||
exports.GLOBALS = globals_1.GLOBALS; | ||
/** | ||
* Default export | ||
*/ | ||
const edge = new Edge_1.Edge(); | ||
globals_1.default(edge); | ||
Object.keys(globals_1.GLOBALS).forEach((key) => edge.global(key, globals_1.GLOBALS[key])); | ||
exports.default = edge; | ||
var Context_1 = require("./src/Context"); | ||
exports.safeValue = Context_1.safeValue; | ||
exports.withCtx = Context_1.withCtx; |
@@ -15,5 +15,4 @@ import { LoaderTemplate, CacheManagerContract } from '../Contracts'; | ||
/** | ||
* Returns the template and the presenter class from the | ||
* cache. If caching is disabled, then it will | ||
* return undefined. | ||
* Returns the template from the cache. If caching is disabled, | ||
* then it will return undefined. | ||
*/ | ||
@@ -23,5 +22,5 @@ get(absPath: string): undefined | LoaderTemplate; | ||
* Set's the template path and the payload to the cache. If | ||
* cache is disabled, then this function returns in noop. | ||
* cache is disabled, then this function results in a noop. | ||
*/ | ||
set(absPath: string, payload: LoaderTemplate): void; | ||
} |
@@ -27,5 +27,4 @@ "use strict"; | ||
/** | ||
* Returns the template and the presenter class from the | ||
* cache. If caching is disabled, then it will | ||
* return undefined. | ||
* Returns the template from the cache. If caching is disabled, | ||
* then it will return undefined. | ||
*/ | ||
@@ -40,3 +39,3 @@ get(absPath) { | ||
* Set's the template path and the payload to the cache. If | ||
* cache is disabled, then this function returns in noop. | ||
* cache is disabled, then this function results in a noop. | ||
*/ | ||
@@ -43,0 +42,0 @@ set(absPath, payload) { |
@@ -0,1 +1,2 @@ | ||
import { Parser } from 'edge-parser'; | ||
import { Token } from 'edge-lexer'; | ||
@@ -5,8 +6,4 @@ import { CacheManager } from '../CacheManager'; | ||
/** | ||
* Compiler compiles the template to a function, which can be invoked at a later | ||
* stage using the [[Context]]. [edge-parser](https://npm.im/edge-parser) is | ||
* used under the hood to parse the templates. | ||
* | ||
* Also, the `layouts` are handled natively by the compiler. Before starting | ||
* the parsing process, it will recursively merge the layout sections. | ||
* Compiler is to used to compile templates using the `edge-parser`. Along with that | ||
* it natively merges the contents of a layout with a parent template. | ||
*/ | ||
@@ -30,3 +27,3 @@ export declare class Compiler implements CompilerContract { | ||
/** | ||
* Converts the template content to an [array of lexer tokens]. The method is | ||
* Converts the template content to an array of lexer tokens. The method is | ||
* same as the `parser.tokenize`, but it also handles layouts natively. | ||
@@ -38,25 +35,13 @@ * | ||
*/ | ||
tokenize(templatePath: string): Token[]; | ||
tokenize(templatePath: string, parser?: Parser): Token[]; | ||
/** | ||
* Compiles the template contents to a function string, which can be invoked | ||
* later. | ||
* Compiles the template contents to string. The output is same as the `edge-parser`, | ||
* it's just that the compiler uses the loader to load the templates and also | ||
* handles layouts. | ||
* | ||
* When `inline` is set to true, the compiled output **will not have it's own scope** and | ||
* neither an attempt to load the presenter is made. The `inline` is mainly used for partials. | ||
* | ||
* ```js | ||
* compiler.compile('welcome', false) | ||
* // output | ||
* | ||
* { | ||
* template: `function (template, ctx) { | ||
* let out = '' | ||
* out += '' | ||
* return out | ||
* })(template, ctx)`, | ||
* Presenter: class Presenter | undefined | ||
* } | ||
* compiler.compile('welcome') | ||
* ``` | ||
*/ | ||
compile(templatePath: string, inline: boolean): LoaderTemplate; | ||
compile(templatePath: string, localVariables?: string[]): LoaderTemplate; | ||
} |
@@ -16,8 +16,4 @@ "use strict"; | ||
/** | ||
* Compiler compiles the template to a function, which can be invoked at a later | ||
* stage using the [[Context]]. [edge-parser](https://npm.im/edge-parser) is | ||
* used under the hood to parse the templates. | ||
* | ||
* Also, the `layouts` are handled natively by the compiler. Before starting | ||
* the parsing process, it will recursively merge the layout sections. | ||
* Compiler is to used to compile templates using the `edge-parser`. Along with that | ||
* it natively merges the contents of a layout with a parent template. | ||
*/ | ||
@@ -47,8 +43,9 @@ class Compiler { | ||
/** | ||
* Ignore new lines, layout tag and empty raw nodes inside the parent | ||
* Ignore new lines, comments, layout tag and empty raw nodes inside the parent | ||
* template | ||
*/ | ||
if (edge_lexer_1.utils.isTag(node, 'layout') || | ||
node.type === 'newline' || | ||
(node.type === 'raw' && !node.value.trim())) { | ||
if (edge_lexer_1.utils.isTag(node, 'layout') | ||
|| node.type === 'newline' | ||
|| (node.type === 'raw' && !node.value.trim()) | ||
|| node.type === 'comment') { | ||
return; | ||
@@ -107,4 +104,4 @@ } | ||
*/ | ||
templateContentToTokens(content, parser) { | ||
let templateTokens = parser.tokenize(content); | ||
templateContentToTokens(content, parser, absPath) { | ||
let templateTokens = parser.tokenize(content, absPath); | ||
const firstToken = templateTokens[0]; | ||
@@ -117,3 +114,3 @@ /** | ||
const layoutName = firstToken.properties.jsArg.replace(/'|"/g, ''); | ||
templateTokens = this.mergeSections(this.tokenize(layoutName), templateTokens); | ||
templateTokens = this.mergeSections(this.tokenize(layoutName, parser), templateTokens); | ||
} | ||
@@ -123,3 +120,3 @@ return templateTokens; | ||
/** | ||
* Converts the template content to an [array of lexer tokens]. The method is | ||
* Converts the template content to an array of lexer tokens. The method is | ||
* same as the `parser.tokenize`, but it also handles layouts natively. | ||
@@ -131,30 +128,17 @@ * | ||
*/ | ||
tokenize(templatePath) { | ||
tokenize(templatePath, parser) { | ||
const absPath = this.loader.makePath(templatePath); | ||
const { template } = this.loader.resolve(absPath, false); | ||
const parser = new edge_parser_1.Parser(this.tags, { filename: absPath }); | ||
return this.templateContentToTokens(template, parser); | ||
const { template } = this.loader.resolve(absPath); | ||
return this.templateContentToTokens(template, parser || new edge_parser_1.Parser(this.tags), absPath); | ||
} | ||
/** | ||
* Compiles the template contents to a function string, which can be invoked | ||
* later. | ||
* Compiles the template contents to string. The output is same as the `edge-parser`, | ||
* it's just that the compiler uses the loader to load the templates and also | ||
* handles layouts. | ||
* | ||
* When `inline` is set to true, the compiled output **will not have it's own scope** and | ||
* neither an attempt to load the presenter is made. The `inline` is mainly used for partials. | ||
* | ||
* ```js | ||
* compiler.compile('welcome', false) | ||
* // output | ||
* | ||
* { | ||
* template: `function (template, ctx) { | ||
* let out = '' | ||
* out += '' | ||
* return out | ||
* })(template, ctx)`, | ||
* Presenter: class Presenter | undefined | ||
* } | ||
* compiler.compile('welcome') | ||
* ``` | ||
*/ | ||
compile(templatePath, inline) { | ||
compile(templatePath, localVariables) { | ||
const absPath = this.loader.makePath(templatePath); | ||
@@ -169,38 +153,19 @@ /** | ||
} | ||
const parser = new edge_parser_1.Parser(this.tags); | ||
const buffer = new edge_parser_1.EdgeBuffer(absPath); | ||
/** | ||
* Do not return presenter in inline mode | ||
* Define local variables on the parser. This is helpful when trying to compile | ||
* a partail and we want to share the local state of the parent template | ||
* with it | ||
*/ | ||
const loadPresenter = !inline; | ||
/** | ||
* Inline templates are not wrapped inside a function | ||
* call. They share the parent template scope | ||
*/ | ||
const wrapAsFunction = !inline; | ||
/** | ||
* Get a new instance of the parser. | ||
*/ | ||
const parser = new edge_parser_1.Parser(this.tags, { filename: absPath }); | ||
/** | ||
* Resolve the template and Presenter using the given loader. We always | ||
* load the presenter but don't return it when `loadPresenter = false`. | ||
*/ | ||
const { template, Presenter } = this.loader.resolve(absPath, true); | ||
/** | ||
* Convert template to AST. The AST will have the layout actions merged (if layout) | ||
* is used. | ||
*/ | ||
const templateTokens = this.templateContentToTokens(template, parser); | ||
/** | ||
* Finally process the ast | ||
*/ | ||
const buffer = new edge_parser_1.EdgeBuffer(absPath, wrapAsFunction); | ||
if (localVariables) { | ||
localVariables.forEach((localVariable) => parser.stack.defineVariable(localVariable)); | ||
} | ||
const templateTokens = this.tokenize(absPath, parser); | ||
templateTokens.forEach((token) => parser.processToken(token, buffer)); | ||
const payload = { | ||
template: buffer.flush(), | ||
Presenter, | ||
}; | ||
this.cacheManager.set(absPath, payload); | ||
return loadPresenter ? payload : { template: payload.template }; | ||
const template = buffer.flush(); | ||
this.cacheManager.set(absPath, { template }); | ||
return { template }; | ||
} | ||
} | ||
exports.Compiler = Compiler; |
import { Macroable } from 'macroable'; | ||
import { Presenter } from '../Presenter'; | ||
import { ContextContract } from '../Contracts'; | ||
@@ -9,3 +8,3 @@ /** | ||
*/ | ||
declare class SafeValue { | ||
export declare class SafeValue { | ||
value: any; | ||
@@ -15,29 +14,10 @@ constructor(value: any); | ||
/** | ||
* A class to wrap callbacks that can access the `ctx` | ||
*/ | ||
declare class WithCtx { | ||
private callback; | ||
constructor(callback: (ctx: ContextContract, ...args: any[]) => any); | ||
/** | ||
* Invoke the callback | ||
*/ | ||
invoke(ctx: ContextContract, bindState: any): (...args: any[]) => any; | ||
} | ||
/** | ||
* Context is used at runtime to resolve values for a given | ||
* template. | ||
* The context passed to templates during Runtime. Context enables tags to | ||
* register custom methods which are available during runtime. | ||
* | ||
* Also the context can be extended to add `getters` and `methods`. Checkout | ||
* [macroable](https://github.com/poppinss/macroable) for same. | ||
* For example: The `@each` tag defines `ctx.loop` method to loop over | ||
* Arrays and Objects. | ||
*/ | ||
export declare class Context extends Macroable implements ContextContract { | ||
presenter: Presenter; | ||
/** | ||
* Frames are used to define a inner scope in which values will | ||
* be resolved. The resolve function starts with the deepest | ||
* frame and then resolve the value up until the first | ||
* frame. | ||
*/ | ||
private frames; | ||
/** | ||
* Required by Macroable | ||
@@ -47,100 +27,13 @@ */ | ||
protected static getters: {}; | ||
constructor(); | ||
/** | ||
* Added by compiler | ||
*/ | ||
$filename: string; | ||
$lineNumber: number; | ||
constructor(presenter: Presenter); | ||
/** | ||
* Returns value for a key inside frames. Stops looking for it, | ||
* when value is found inside any frame. | ||
*/ | ||
private getFromFrame; | ||
/** | ||
* Returns a merged copy of the current state. The objects are merged | ||
* in the same order as they are resolved. | ||
*/ | ||
private getCurrentState; | ||
/** | ||
* Creates a new frame scope. Think of a scope as a Javacript block | ||
* scope, where variables defined inside the scope are only available | ||
* to that scope. | ||
* | ||
* ```js | ||
* ctx.newFrame() | ||
* ``` | ||
*/ | ||
newFrame(): void; | ||
/** | ||
* Set key/value pair on the frame object. The value will only be available til | ||
* the `removeFrame` is not called. | ||
* | ||
* ```js | ||
* ctx.setOnFrame('username', 'virk') | ||
* | ||
* // nested values | ||
* ctx.setOnFrame('user.username', 'virk') | ||
* ``` | ||
* | ||
* @throws Error if no frame scopes exists. | ||
*/ | ||
setOnFrame(key: string, value: any): void; | ||
/** | ||
* Removes the most recent frame/scope. All values set inside the | ||
* frame via `setOnFrame` will be removed. | ||
*/ | ||
removeFrame(): void; | ||
/** | ||
* Returns all the frames | ||
*/ | ||
getFrames(): any[]; | ||
/** | ||
* Mark output as safe | ||
*/ | ||
safe<T extends any>(value: T): SafeValue; | ||
/** | ||
* Escapes the value to be HTML safe. Only strings are escaped | ||
* and rest all values will be returned as it is. | ||
*/ | ||
escape<T>(input: T): T; | ||
escape<T>(input: T): T extends SafeValue ? T['value'] : T; | ||
/** | ||
* Transform the resolved value before returning it | ||
* back | ||
*/ | ||
private transformValue; | ||
/** | ||
* Resolves value for a given key. It will look for the value in different | ||
* locations and continues till the end if `undefined` is returned at | ||
* each step. | ||
* | ||
* The following steps are followed in the same order as defined. | ||
* | ||
* 1. Check for value inside frames. | ||
* 2. Then on the presenter instance. | ||
* 3. Then the presenter `state` object. | ||
* 4. Finally fallback to the sharedState. | ||
* | ||
* @example | ||
* ```js | ||
* ctx.resolve('username') | ||
* ``` | ||
*/ | ||
resolve(key: string): any; | ||
/** | ||
* Set/Update the value in the context. The value is defined in the following | ||
* order. | ||
* | ||
* 1. If the scope is inside a frame, then will be created/updated on the frame. | ||
* 2. Otherwise, the value is created on the presenter state. | ||
* | ||
* ```js | ||
* ctx.set('username', 'virk') | ||
* ``` | ||
*/ | ||
set(key: string, value: any, isolated?: boolean): void; | ||
/** | ||
* Rethrows the runtime exception by re-constructing the error message | ||
* to point back to the original filename | ||
*/ | ||
reThrow(error: any): void; | ||
reThrow(error: any, filename: string, lineNumber: number): never; | ||
} | ||
@@ -151,7 +44,1 @@ /** | ||
export declare function safeValue(value: string): SafeValue; | ||
/** | ||
* Wrap a function that receives the template engine current | ||
* ctx when invoked. | ||
*/ | ||
export declare function withCtx(callback: (ctx: ContextContract, ...args: any[]) => any): WithCtx; | ||
export {}; |
@@ -15,3 +15,2 @@ "use strict"; | ||
const he_1 = __importDefault(require("he")); | ||
const lodash_1 = require("lodash"); | ||
const macroable_1 = require("macroable"); | ||
@@ -29,109 +28,15 @@ const edge_error_1 = require("edge-error"); | ||
} | ||
exports.SafeValue = SafeValue; | ||
/** | ||
* A class to wrap callbacks that can access the `ctx` | ||
*/ | ||
class WithCtx { | ||
constructor(callback) { | ||
this.callback = callback; | ||
} | ||
/** | ||
* Invoke the callback | ||
*/ | ||
invoke(ctx, bindState) { | ||
return (...args) => { | ||
return this.callback.bind(bindState)(ctx, ...args); | ||
}; | ||
} | ||
} | ||
/** | ||
* Context is used at runtime to resolve values for a given | ||
* template. | ||
* The context passed to templates during Runtime. Context enables tags to | ||
* register custom methods which are available during runtime. | ||
* | ||
* Also the context can be extended to add `getters` and `methods`. Checkout | ||
* [macroable](https://github.com/poppinss/macroable) for same. | ||
* For example: The `@each` tag defines `ctx.loop` method to loop over | ||
* Arrays and Objects. | ||
*/ | ||
class Context extends macroable_1.Macroable { | ||
constructor(presenter) { | ||
constructor() { | ||
super(); | ||
this.presenter = presenter; | ||
/** | ||
* Frames are used to define a inner scope in which values will | ||
* be resolved. The resolve function starts with the deepest | ||
* frame and then resolve the value up until the first | ||
* frame. | ||
*/ | ||
this.frames = []; | ||
/** | ||
* Added by compiler | ||
*/ | ||
this.$filename = ''; | ||
this.$lineNumber = 0; | ||
} | ||
/** | ||
* Returns value for a key inside frames. Stops looking for it, | ||
* when value is found inside any frame. | ||
*/ | ||
getFromFrame(key) { | ||
const frameWithVal = this.frames.find((frame) => frame[key] !== undefined); | ||
return frameWithVal ? frameWithVal[key] : undefined; | ||
} | ||
/** | ||
* Returns a merged copy of the current state. The objects are merged | ||
* in the same order as they are resolved. | ||
*/ | ||
getCurrentState() { | ||
return Object.assign({}, this.presenter.sharedState, this.presenter.state, ...this.frames); | ||
} | ||
/** | ||
* Creates a new frame scope. Think of a scope as a Javacript block | ||
* scope, where variables defined inside the scope are only available | ||
* to that scope. | ||
* | ||
* ```js | ||
* ctx.newFrame() | ||
* ``` | ||
*/ | ||
newFrame() { | ||
this.frames.unshift({}); | ||
} | ||
/** | ||
* Set key/value pair on the frame object. The value will only be available til | ||
* the `removeFrame` is not called. | ||
* | ||
* ```js | ||
* ctx.setOnFrame('username', 'virk') | ||
* | ||
* // nested values | ||
* ctx.setOnFrame('user.username', 'virk') | ||
* ``` | ||
* | ||
* @throws Error if no frame scopes exists. | ||
*/ | ||
setOnFrame(key, value) { | ||
const recentFrame = this.frames[0]; | ||
if (!recentFrame) { | ||
throw new Error('Make sure to call "newFrame" before calling "setOnFrame"'); | ||
} | ||
lodash_1.set(recentFrame, key, value); | ||
} | ||
/** | ||
* Removes the most recent frame/scope. All values set inside the | ||
* frame via `setOnFrame` will be removed. | ||
*/ | ||
removeFrame() { | ||
this.frames.shift(); | ||
} | ||
/** | ||
* Returns all the frames | ||
*/ | ||
getFrames() { | ||
return this.frames; | ||
} | ||
/** | ||
* Mark output as safe | ||
*/ | ||
safe(value) { | ||
return new SafeValue(value); | ||
} | ||
/** | ||
* Escapes the value to be HTML safe. Only strings are escaped | ||
@@ -146,125 +51,13 @@ * and rest all values will be returned as it is. | ||
/** | ||
* Transform the resolved value before returning it | ||
* back | ||
*/ | ||
transformValue(value, bindState) { | ||
if (value instanceof WithCtx) { | ||
return value.invoke(this, bindState); | ||
} | ||
if (typeof (value) === 'function') { | ||
return value.bind(bindState); | ||
} | ||
return value; | ||
} | ||
/** | ||
* Resolves value for a given key. It will look for the value in different | ||
* locations and continues till the end if `undefined` is returned at | ||
* each step. | ||
* | ||
* The following steps are followed in the same order as defined. | ||
* | ||
* 1. Check for value inside frames. | ||
* 2. Then on the presenter instance. | ||
* 3. Then the presenter `state` object. | ||
* 4. Finally fallback to the sharedState. | ||
* | ||
* @example | ||
* ```js | ||
* ctx.resolve('username') | ||
* ``` | ||
*/ | ||
resolve(key) { | ||
/** | ||
* A special key to return the template current state | ||
*/ | ||
if (key === '$state') { | ||
return this.getCurrentState(); | ||
} | ||
/** | ||
* A special key to return the filename of the current execution | ||
* scope. | ||
*/ | ||
if (key === '$filename') { | ||
return this.$filename; | ||
} | ||
/** | ||
* A special key to return the current execution line number pointing | ||
* fowards the original template file. | ||
*/ | ||
if (key === '$lineNumber') { | ||
return this.$lineNumber; | ||
} | ||
let value; | ||
/** | ||
* Pull from one of the nested frames | ||
*/ | ||
value = this.getFromFrame(key); | ||
if (value !== undefined) { | ||
return this.transformValue(value, this); | ||
} | ||
/** | ||
* Check for value as a property on the presenter | ||
* itself. | ||
*/ | ||
value = this.presenter[key]; | ||
if (value !== undefined) { | ||
return this.transformValue(value, this.presenter); | ||
} | ||
/** | ||
* Otherwise look into presenter state | ||
*/ | ||
value = this.presenter.state[key]; | ||
if (value !== undefined) { | ||
return this.transformValue(value, this.presenter.state); | ||
} | ||
/** | ||
* Finally fallback to shared globals | ||
*/ | ||
value = this.presenter.sharedState[key]; | ||
return this.transformValue(value, this.presenter.sharedState); | ||
} | ||
/** | ||
* Set/Update the value in the context. The value is defined in the following | ||
* order. | ||
* | ||
* 1. If the scope is inside a frame, then will be created/updated on the frame. | ||
* 2. Otherwise, the value is created on the presenter state. | ||
* | ||
* ```js | ||
* ctx.set('username', 'virk') | ||
* ``` | ||
*/ | ||
set(key, value, isolated = false) { | ||
/** | ||
* Set value on the presenter state if it already exists and user | ||
* doesn't want an isolated state for the current frame scope | ||
*/ | ||
if (lodash_1.get(this.presenter.state, key) !== undefined && !isolated) { | ||
lodash_1.set(this.presenter.state, key, value); | ||
return; | ||
} | ||
/** | ||
* If frames exists, then set the value on the framework | ||
*/ | ||
if (this.frames.length) { | ||
this.setOnFrame(key, value); | ||
return; | ||
} | ||
/** | ||
* Otherwise set on presenter | ||
*/ | ||
lodash_1.set(this.presenter.state, key, value); | ||
} | ||
/** | ||
* Rethrows the runtime exception by re-constructing the error message | ||
* to point back to the original filename | ||
*/ | ||
reThrow(error) { | ||
reThrow(error, filename, lineNumber) { | ||
if (error instanceof edge_error_1.EdgeError) { | ||
throw error; | ||
} | ||
// const message = error.message.replace(/ctx\.resolve\(\.\.\.\)/, this.lastResolvedKey) | ||
throw new edge_error_1.EdgeError(error.message, 'E_RUNTIME_EXCEPTION', { | ||
filename: this.$filename, | ||
line: this.$lineNumber, | ||
const message = error.message.replace(/state\./, ''); | ||
throw new edge_error_1.EdgeError(message, 'E_RUNTIME_EXCEPTION', { | ||
filename: filename, | ||
line: lineNumber, | ||
col: 0, | ||
@@ -287,9 +80,1 @@ }); | ||
exports.safeValue = safeValue; | ||
/** | ||
* Wrap a function that receives the template engine current | ||
* ctx when invoked. | ||
*/ | ||
function withCtx(callback) { | ||
return new WithCtx(callback); | ||
} | ||
exports.withCtx = withCtx; |
@@ -9,5 +9,5 @@ /** | ||
*/ | ||
import { Token } from 'edge-lexer'; | ||
import { Token, TagToken } from 'edge-lexer'; | ||
import { MacroableConstructorContract } from 'macroable'; | ||
import { ParserTagDefininationContract } from 'edge-parser'; | ||
import { ParserTagDefinitionContract, Parser, EdgeBuffer } from 'edge-parser'; | ||
/** | ||
@@ -18,5 +18,2 @@ * The shape in which the loader must resolve the template | ||
template: string; | ||
Presenter?: { | ||
new (state: any): any; | ||
}; | ||
}; | ||
@@ -44,3 +41,3 @@ /** | ||
*/ | ||
resolve(templatePath: string, withPresenter: boolean): LoaderTemplate; | ||
resolve(templatePath: string): LoaderTemplate; | ||
/** | ||
@@ -56,22 +53,7 @@ * Make absolute path to a template | ||
/** | ||
* Shape of a view presenter | ||
*/ | ||
export interface PresenterContract { | ||
state: any; | ||
sharedState: any; | ||
} | ||
/** | ||
* Shape of runtime context | ||
*/ | ||
export interface ContextContract { | ||
presenter: PresenterContract; | ||
safe<T extends any>(value: T): { | ||
value: T; | ||
}; | ||
newFrame(): void; | ||
setOnFrame(key: string, value: any): void; | ||
removeFrame(): void; | ||
escape<T>(input: T): T; | ||
resolve(key: string): any; | ||
set(key: string, value: any): void; | ||
reThrow(error: any, filename: string, linenumber: number): never; | ||
} | ||
@@ -82,9 +64,9 @@ /** | ||
export interface ContextConstructorContract extends MacroableConstructorContract<ContextContract> { | ||
new (presenter: any, sharedState: any): ContextContract; | ||
new (): ContextContract; | ||
} | ||
/** | ||
* The final tag must have a tagName along with other properties | ||
* The tag must have a tagName along with other properties | ||
* required by lexer and parser | ||
*/ | ||
export interface TagContract extends ParserTagDefininationContract { | ||
export interface TagContract extends ParserTagDefinitionContract { | ||
tagName: string; | ||
@@ -112,4 +94,4 @@ run?(context: ContextConstructorContract): void; | ||
cacheManager: CacheManagerContract; | ||
compile(templatePath: string, inline: boolean): LoaderTemplate; | ||
tokenize(templatePath: string): Token[]; | ||
compile(templatePath: string, localVariables?: string[]): LoaderTemplate; | ||
tokenize(templatePath: string, parser?: Parser): Token[]; | ||
} | ||
@@ -147,1 +129,7 @@ /** | ||
} | ||
/** | ||
* Required for someone creating custom tags | ||
*/ | ||
export declare type EdgeBufferContract = EdgeBuffer; | ||
export declare type ParserContract = Parser; | ||
export declare type TagTokenContract = TagToken; |
@@ -1,5 +0,13 @@ | ||
import { EdgeContract } from '../../Contracts'; | ||
/** | ||
* A list of default globals | ||
*/ | ||
export default function globals(edge: EdgeContract): void; | ||
import { safeValue } from '../../Context'; | ||
export declare const GLOBALS: { | ||
inspect: (value: any) => import("../../Context").SafeValue; | ||
truncate: (value: string, length?: number, options?: { | ||
strict: boolean; | ||
suffix: string; | ||
} | undefined) => any; | ||
excerpt: (value: string, length?: number, options?: { | ||
strict: boolean; | ||
suffix: string; | ||
} | undefined) => any; | ||
safe: typeof safeValue; | ||
}; |
"use strict"; | ||
/* | ||
* edge | ||
* | ||
* (c) Harminder Virk <virk@adonisjs.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
* edge.js | ||
* | ||
* (c) Harminder Virk <virk@adonisjs.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const util_1 = require("util"); | ||
const lodash_1 = require("lodash"); | ||
const truncatise_1 = __importDefault(require("truncatise")); | ||
const PrettyPrint_1 = require("./PrettyPrint"); | ||
const Context_1 = require("../../Context"); | ||
/** | ||
* Inspect value. | ||
*/ | ||
function inspect(ctx, valueToInspect, depth = 1) { | ||
const inspectedString = `<pre>${util_1.inspect(valueToInspect, { | ||
showHidden: true, | ||
compact: false, | ||
depth: depth, | ||
})}</pre>`; | ||
const filename = `<span style="color: #999; position: absolute; right: 20px; top: 10px;"> | ||
${ctx.resolve('$filename')} | ||
</span>`; | ||
return Context_1.safeValue(`<div class="__inspect_output" style="background: #000; color: #fff; padding: 20px; position: relative;">${inspectedString}${filename}</div>`); | ||
} | ||
/** | ||
* Compacting the inspect output of self | ||
*/ | ||
inspect[Symbol.for('nodejs.util.inspect.custom')] = function customInspect() { | ||
return '[inspect]'; | ||
exports.GLOBALS = { | ||
inspect: (value) => { | ||
return Context_1.safeValue(new PrettyPrint_1.PrettyPrint().print(value)); | ||
}, | ||
truncate: (value, length = 20, options) => { | ||
return truncatise_1.default(value, { | ||
Strict: !!(options === null || options === void 0 ? void 0 : options.strict), | ||
StripHTML: false, | ||
TruncateLength: length, | ||
TruncateBy: 'characters', | ||
Suffix: options === null || options === void 0 ? void 0 : options.suffix, | ||
}); | ||
}, | ||
excerpt: (value, length = 20, options) => { | ||
return truncatise_1.default(value, { | ||
Strict: !!(options === null || options === void 0 ? void 0 : options.strict), | ||
StripHTML: true, | ||
TruncateLength: length, | ||
TruncateBy: 'characters', | ||
Suffix: options === null || options === void 0 ? void 0 : options.suffix, | ||
}); | ||
}, | ||
safe: Context_1.safeValue, | ||
}; | ||
/** | ||
* A list of default globals | ||
*/ | ||
function globals(edge) { | ||
edge.global('inspect', Context_1.withCtx(inspect)); | ||
edge.global('range', (start, end, step) => lodash_1.range(start, end, step)); | ||
edge.global('first', lodash_1.first); | ||
edge.global('last', lodash_1.last); | ||
edge.global('groupBy', lodash_1.groupBy); | ||
edge.global('size', lodash_1.size); | ||
edge.global('truncate', lodash_1.truncate); | ||
edge.global('toAnchor', (url, title = url) => { | ||
return Context_1.safeValue(`<a href="${url}"> ${title} </a>`); | ||
}); | ||
edge.global('style', (url, title = url) => { | ||
return Context_1.safeValue(`<a href="${url}"> ${title} </a>`); | ||
}); | ||
} | ||
exports.default = globals; |
import { Compiler } from '../Compiler'; | ||
import { TagContract, EdgeOptions, EdgeContract, LoaderTemplate, EdgeRendererContract } from '../Contracts'; | ||
/** | ||
* Exposes the API to render templates, register custom tags and globals | ||
*/ | ||
export declare class Edge implements EdgeContract { | ||
@@ -4,0 +7,0 @@ private options; |
@@ -23,2 +23,5 @@ "use strict"; | ||
const Renderer_1 = require("../Renderer"); | ||
/** | ||
* Exposes the API to render templates, register custom tags and globals | ||
*/ | ||
class Edge { | ||
@@ -25,0 +28,0 @@ constructor(options = {}) { |
@@ -11,5 +11,5 @@ /** | ||
/** | ||
* The job of a loader is to load the template and it's presenter for a given path. | ||
* The base loader (shipped with edge) looks for files on the file-system and | ||
* reads them synchronously. | ||
* The job of a loader is to load the template from a given path. | ||
* The base loader (shipped with edge) looks for files on the | ||
* file-system and reads them synchronously. | ||
* | ||
@@ -28,10 +28,2 @@ * You are free to define your own loaders that implements the [[LoaderContract]] interface. | ||
/** | ||
* Attempts to load the presenter for a given template. If presenter doesn't exists, it | ||
* will swallow the error. | ||
* | ||
* Also this method will **bypass the `require` cache**, since in production compiled templates | ||
* and their presenters are cached anyways. | ||
*/ | ||
private getPresenterForTemplate; | ||
/** | ||
* Reads the content of a template from the disk. An exception is raised | ||
@@ -108,10 +100,4 @@ * when file is missing or if `readFileSync` returns an error. | ||
/** | ||
* Resolves the template from the disk, optionally loads the presenter too. The presenter | ||
* resolution is based on the convention and resolved from the same directory | ||
* as the template. | ||
* Resolves the template by reading its contents from the disk | ||
* | ||
* ## Presenter convention | ||
* - View name - welcome.edge | ||
* - Presenter name - Welcome.presenter.js | ||
* | ||
* ```js | ||
@@ -123,9 +109,8 @@ * loader.resolve('welcome', true) | ||
* template: `<h1> Template content </h1>`, | ||
* Presenter: class Presenter | undefined | ||
* } | ||
* ``` | ||
*/ | ||
resolve(templatePath: string, withPresenter: boolean): LoaderTemplate; | ||
resolve(templatePath: string): LoaderTemplate; | ||
/** | ||
* Register in memory template and Presenter for a given path. This is super helpful | ||
* Register in memory template for a given path. This is super helpful | ||
* when distributing components. | ||
@@ -132,0 +117,0 @@ * |
@@ -10,14 +10,9 @@ "use strict"; | ||
*/ | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const fs_1 = require("fs"); | ||
const import_fresh_1 = __importDefault(require("import-fresh")); | ||
const path_1 = require("path"); | ||
const utils_1 = require("@poppinss/utils"); | ||
/** | ||
* The job of a loader is to load the template and it's presenter for a given path. | ||
* The base loader (shipped with edge) looks for files on the file-system and | ||
* reads them synchronously. | ||
* The job of a loader is to load the template from a given path. | ||
* The base loader (shipped with edge) looks for files on the | ||
* file-system and reads them synchronously. | ||
* | ||
@@ -38,22 +33,2 @@ * You are free to define your own loaders that implements the [[LoaderContract]] interface. | ||
/** | ||
* Attempts to load the presenter for a given template. If presenter doesn't exists, it | ||
* will swallow the error. | ||
* | ||
* Also this method will **bypass the `require` cache**, since in production compiled templates | ||
* and their presenters are cached anyways. | ||
*/ | ||
getPresenterForTemplate(templatePath) { | ||
const presenterPath = templatePath | ||
.replace(/^\w/, c => c.toUpperCase()) | ||
.replace(path_1.extname(templatePath), '.presenter.js'); | ||
try { | ||
return utils_1.esmResolver(import_fresh_1.default(presenterPath)); | ||
} | ||
catch (error) { | ||
if (['ENOENT', 'MODULE_NOT_FOUND'].indexOf(error.code) === -1) { | ||
throw error; | ||
} | ||
} | ||
} | ||
/** | ||
* Reads the content of a template from the disk. An exception is raised | ||
@@ -68,3 +43,3 @@ * when file is missing or if `readFileSync` returns an error. | ||
if (error.code === 'ENOENT') { | ||
throw new utils_1.Exception(`Cannot resolve "${absPath}". Make sure the file exists`, 500, 'E_MISSING_TEMPLATE_FILE'); | ||
throw new Error(`Cannot resolve "${absPath}". Make sure the file exists`); | ||
} | ||
@@ -179,3 +154,3 @@ else { | ||
if (!mountedDir) { | ||
throw new utils_1.Exception(`"${diskName}" namespace is not mounted`, 500, 'E_UNMOUNTED_DISK_NAME'); | ||
throw new Error(`"${diskName}" namespace is not mounted`); | ||
} | ||
@@ -185,10 +160,4 @@ return path_1.join(mountedDir, template); | ||
/** | ||
* Resolves the template from the disk, optionally loads the presenter too. The presenter | ||
* resolution is based on the convention and resolved from the same directory | ||
* as the template. | ||
* Resolves the template by reading its contents from the disk | ||
* | ||
* ## Presenter convention | ||
* - View name - welcome.edge | ||
* - Presenter name - Welcome.presenter.js | ||
* | ||
* ```js | ||
@@ -200,7 +169,6 @@ * loader.resolve('welcome', true) | ||
* template: `<h1> Template content </h1>`, | ||
* Presenter: class Presenter | undefined | ||
* } | ||
* ``` | ||
*/ | ||
resolve(templatePath, withPresenter) { | ||
resolve(templatePath) { | ||
/** | ||
@@ -210,4 +178,3 @@ * Return from pre-registered one's if exists | ||
if (this.preRegistered.has(templatePath)) { | ||
const contents = this.preRegistered.get(templatePath); | ||
return withPresenter ? contents : { template: contents.template }; | ||
return this.preRegistered.get(templatePath); | ||
} | ||
@@ -220,7 +187,6 @@ /** | ||
template: this.readTemplateContents(templatePath), | ||
Presenter: withPresenter ? this.getPresenterForTemplate(templatePath) : undefined, | ||
}; | ||
} | ||
/** | ||
* Register in memory template and Presenter for a given path. This is super helpful | ||
* Register in memory template for a given path. This is super helpful | ||
* when distributing components. | ||
@@ -246,3 +212,3 @@ * | ||
if (typeof (contents.template) !== 'string') { | ||
throw new utils_1.Exception('Make sure to define the template content as a string', 500, 'E_MISSING_TEMPLATE_CONTENTS'); | ||
throw new Error('Make sure to define the template content as a string'); | ||
} | ||
@@ -253,3 +219,3 @@ /** | ||
if (this.preRegistered.has(templatePath)) { | ||
throw new utils_1.Exception(`Cannot override previously registered "${templatePath}" template`, 500, 'E_DUPLICATE_TEMPLATE_PATH'); | ||
throw new Error(`Cannot override previously registered "${templatePath}" template`); | ||
} | ||
@@ -256,0 +222,0 @@ this.preRegistered.set(templatePath, contents); |
@@ -10,4 +10,7 @@ "use strict"; | ||
*/ | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const lodash_1 = require("lodash"); | ||
const lodash_merge_1 = __importDefault(require("lodash.merge")); | ||
const Template_1 = require("../Template"); | ||
@@ -28,3 +31,3 @@ /** | ||
share(data) { | ||
lodash_1.merge(this.locals, data); | ||
lodash_merge_1.default(this.locals, data); | ||
return this; | ||
@@ -31,0 +34,0 @@ } |
@@ -17,3 +17,3 @@ import { Parser } from 'edge-parser'; | ||
*/ | ||
add(key: any, value: any): void; | ||
add(key: any, value: any, isComputed?: boolean): void; | ||
/** | ||
@@ -20,0 +20,0 @@ * Returns the object alike string back. |
@@ -28,3 +28,4 @@ "use strict"; | ||
*/ | ||
add(key, value) { | ||
add(key, value, isComputed = false) { | ||
key = isComputed ? `[${key}]` : key; | ||
this.obj += this.obj.length ? `, ${key}: ${value}` : `${key}: ${value}`; | ||
@@ -72,3 +73,3 @@ } | ||
const value = parser.utils.stringify(prop.value); | ||
objectifyString.add(key, value); | ||
objectifyString.add(key, value, prop.computed); | ||
}); | ||
@@ -75,0 +76,0 @@ } |
@@ -48,3 +48,3 @@ "use strict"; | ||
utils_1.isSubsetOf(name, ALLOWED_EXPRESSION_FOR_COMPONENT_NAME, () => { | ||
utils_1.unallowedExpression(`"${parser.utils.stringify(name)}" is not a valid argument for component name`, name, filename); | ||
utils_1.unallowedExpression(`"${parser.utils.stringify(name)}" is not a valid argument for component name`, filename, parser.utils.getExpressionLoc(name)); | ||
}); | ||
@@ -76,3 +76,3 @@ /** | ||
utils_1.isSubsetOf(parsed, [edge_parser_1.expressions.Literal, edge_parser_1.expressions.SequenceExpression], () => { | ||
utils_1.unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @slot tag`, parsed, token.filename); | ||
utils_1.unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @slot tag`, token.filename, parser.utils.getExpressionLoc(parsed)); | ||
}); | ||
@@ -93,3 +93,3 @@ /** | ||
utils_1.isSubsetOf(name, [edge_parser_1.expressions.Literal], () => { | ||
utils_1.unallowedExpression('slot name must be a valid string literal', name, token.filename); | ||
utils_1.unallowedExpression('slot name must be a valid string literal', token.filename, parser.utils.getExpressionLoc(name)); | ||
}); | ||
@@ -115,3 +115,3 @@ /** | ||
utils_1.isSubsetOf(parsed.expressions[1], [edge_parser_1.expressions.Identifier], () => { | ||
utils_1.unallowedExpression(`"${parser.utils.stringify(parsed.expressions[1])}" is not valid prop identifier for @slot tag`, parsed.expressions[1], token.filename); | ||
utils_1.unallowedExpression(`"${parser.utils.stringify(parsed.expressions[1])}" is not valid prop identifier for @slot tag`, token.filename, parser.utils.getExpressionLoc(parsed.expressions[1])); | ||
}); | ||
@@ -132,3 +132,3 @@ /** | ||
compile(parser, buffer, token) { | ||
const parsed = parser.utils.transformAst(parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename); | ||
const parsed = utils_1.parseJsArg(parser, token); | ||
/** | ||
@@ -138,3 +138,3 @@ * Check component jsProps for allowed expressions | ||
utils_1.isSubsetOf(parsed, ALLOWED_EXPRESSION_FOR_COMPONENT_NAME.concat(edge_parser_1.expressions.SequenceExpression), () => { | ||
utils_1.unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @component tag`, parsed, token.filename); | ||
utils_1.unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @component tag`, token.filename, parser.utils.getExpressionLoc(parsed)); | ||
}); | ||
@@ -156,11 +156,13 @@ /** | ||
const mainSlot = { | ||
outputVar: 'slot_main', | ||
props: {}, | ||
buffer: new edge_parser_1.EdgeBuffer(token.filename, false, { outputVar: 'slot_main' }), | ||
buffer: new edge_parser_1.EdgeBuffer(token.filename, { outputVar: 'slot_main' }), | ||
line: -1, | ||
filename: token.filename, | ||
}; | ||
let slotsCounter = 0; | ||
/** | ||
* Loop over all the component children | ||
*/ | ||
token.children.forEach((child, index) => { | ||
token.children.forEach((child) => { | ||
/** | ||
@@ -170,2 +172,8 @@ * If children is not a slot, then add it to the main slot | ||
if (!edge_lexer_1.utils.isTag(child, 'slot')) { | ||
/** | ||
* Ignore first newline inside slots | ||
*/ | ||
if (mainSlot.buffer.size === 0 && child.type === 'newline') { | ||
return; | ||
} | ||
parser.processToken(child, mainSlot.buffer); | ||
@@ -178,4 +186,5 @@ return; | ||
const [slotName, slotProps] = getSlotNameAndProps(child, parser); | ||
slotsCounter++; | ||
/** | ||
* Create a new slot with buffer to process the childs | ||
* Create a new slot with buffer to process the children | ||
*/ | ||
@@ -188,3 +197,4 @@ if (!slots[slotName]) { | ||
slots[slotName] = { | ||
buffer: new edge_parser_1.EdgeBuffer(token.filename, false, { outputVar: `slot_${index}` }), | ||
outputVar: `slot_${slotsCounter}`, | ||
buffer: new edge_parser_1.EdgeBuffer(token.filename, { outputVar: `slot_${slotsCounter}` }), | ||
props: slotProps, | ||
@@ -198,4 +208,4 @@ line: -1, | ||
if (slotProps) { | ||
slots[slotName].buffer.writeExpression('ctx.newFrame()', slots[slotName].filename, slots[slotName].line); | ||
slots[slotName].buffer.writeExpression(`ctx.setOnFrame('${slotProps}', ${slotProps})`, slots[slotName].filename, slots[slotName].line); | ||
parser.stack.defineScope(); | ||
parser.stack.defineVariable(slotProps); | ||
} | ||
@@ -206,3 +216,8 @@ } | ||
*/ | ||
child.children.forEach((grandChildren) => parser.processToken(grandChildren, slots[slotName].buffer)); | ||
child.children.forEach((grandChildren) => { | ||
if (slots[slotName].buffer.size === 0 && grandChildren.type === 'newline') { | ||
return; | ||
} | ||
parser.processToken(grandChildren, slots[slotName].buffer); | ||
}); | ||
/** | ||
@@ -212,3 +227,3 @@ * Close the frame after process the slot children | ||
if (slotProps) { | ||
slots[slotName].buffer.writeExpression('ctx.removeFrame()', slots[slotName].filename, slots[slotName].line); | ||
parser.stack.clearScope(); | ||
} | ||
@@ -224,3 +239,3 @@ }); | ||
mainSlot.buffer.wrap('function () {', '}'); | ||
obj.add('main', mainSlot.buffer.flush()); | ||
obj.add('main', mainSlot.buffer.disableFileAndLineVariables().flush()); | ||
} | ||
@@ -239,3 +254,3 @@ else { | ||
slots[slotName].buffer.wrap(fnCall, '}'); | ||
obj.add(slotName, slots[slotName].buffer.flush()); | ||
obj.add(slotName, slots[slotName].buffer.disableFileAndLineVariables().flush()); | ||
} | ||
@@ -242,0 +257,0 @@ else { |
import { TagContract } from '../Contracts'; | ||
/** | ||
* Add debugger break point to the compiled template | ||
* | ||
* ```edge | ||
* @debugger | ||
* ``` | ||
*/ | ||
export declare const debuggerTag: TagContract; |
@@ -13,6 +13,2 @@ "use strict"; | ||
* Add debugger break point to the compiled template | ||
* | ||
* ```edge | ||
* @debugger | ||
* ``` | ||
*/ | ||
@@ -24,3 +20,3 @@ exports.debuggerTag = { | ||
/** | ||
* Compiles else block node to Javascript else statement | ||
* Compiles `@debugger` tags | ||
*/ | ||
@@ -27,0 +23,0 @@ compile(_, buffer, token) { |
@@ -6,4 +6,4 @@ import { TagContract } from '../Contracts'; | ||
* ```edge | ||
* @each(user in users) | ||
* {{ user }} {{ $loop.index }} | ||
* @each((user, index) in users) | ||
* {{ user }} {{ index }} | ||
* @endeach | ||
@@ -10,0 +10,0 @@ * ``` |
@@ -10,19 +10,32 @@ "use strict"; | ||
*/ | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const lodash_size_1 = __importDefault(require("lodash.size")); | ||
const lodash_foreach_1 = __importDefault(require("lodash.foreach")); | ||
const edge_lexer_1 = require("edge-lexer"); | ||
const edge_parser_1 = require("edge-parser"); | ||
const lodash_1 = require("lodash"); | ||
const utils_1 = require("../utils"); | ||
/** | ||
* Returns the list to loop over for the each expression | ||
* Returns the list to loop over for the each binary expression | ||
*/ | ||
function getLoopList(expression, parser, filename) { | ||
return parser.utils.stringify(parser.utils.transformAst(expression, filename)); | ||
function getLoopList(rhsExpression, parser, filename) { | ||
return parser.utils.stringify(parser.utils.transformAst(rhsExpression, filename, parser)); | ||
} | ||
/** | ||
* Returns loop item and the index for the each expression | ||
* Returns loop item and the index for the each binary expression | ||
*/ | ||
function getLoopItemAndIndex(expression, filename) { | ||
utils_1.isSubsetOf(expression, [edge_parser_1.expressions.SequenceExpression, edge_parser_1.expressions.Identifier], () => { | ||
utils_1.unallowedExpression(`invalid left hand side "${expression.type}" for the @each tag`, expression, filename); | ||
function getLoopItemAndIndex(lhsExpression, parser, filename) { | ||
/** | ||
* Ensure the LHS content inside `@each()` curly braces is a `SequenceExpression` or | ||
* `Identifier`. Anything else is not allowed. | ||
* | ||
* For example: | ||
* | ||
* - In `@each(user in users)`, `user` is an indentifier | ||
* - In `@each((user, index) in users)`, `(user, index)` is a sequence expression | ||
*/ | ||
utils_1.isSubsetOf(lhsExpression, [edge_parser_1.expressions.SequenceExpression, edge_parser_1.expressions.Identifier], () => { | ||
utils_1.unallowedExpression(`invalid left hand side "${lhsExpression.type}" expression for the @each tag`, filename, parser.utils.getExpressionLoc(lhsExpression)); | ||
}); | ||
@@ -32,12 +45,21 @@ /** | ||
*/ | ||
if (expression.type === 'SequenceExpression') { | ||
utils_1.isSubsetOf(expression.expressions[0], [edge_parser_1.expressions.Identifier], () => { | ||
utils_1.unallowedExpression(`"${expression.expressions[0]}.type" is not allowed as value identifier for @each tag`, expression.expressions[0], filename); | ||
if (lhsExpression.type === 'SequenceExpression') { | ||
/** | ||
* First item of the sequence expression must be an idenifier | ||
*/ | ||
utils_1.isSubsetOf(lhsExpression.expressions[0], [edge_parser_1.expressions.Identifier], () => { | ||
utils_1.unallowedExpression(`"${lhsExpression.expressions[0]}.type" is not allowed as value identifier for @each tag`, filename, parser.utils.getExpressionLoc(lhsExpression.expressions[0])); | ||
}); | ||
utils_1.isSubsetOf(expression.expressions[1], [edge_parser_1.expressions.Identifier], () => { | ||
utils_1.unallowedExpression(`"${expression.expressions[1]}.type" is not allowed as key identifier for @each tag`, expression.expressions[1], filename); | ||
/** | ||
* Second item of the sequence expression must be an idenifier | ||
*/ | ||
utils_1.isSubsetOf(lhsExpression.expressions[1], [edge_parser_1.expressions.Identifier], () => { | ||
utils_1.unallowedExpression(`"${lhsExpression.expressions[1]}.type" is not allowed as key identifier for @each tag`, filename, parser.utils.getExpressionLoc(lhsExpression.expressions[1])); | ||
}); | ||
return [expression.expressions[0].name, expression.expressions[1].name]; | ||
return [lhsExpression.expressions[0].name, lhsExpression.expressions[1].name]; | ||
} | ||
return [expression.name, 'key']; | ||
/** | ||
* There is no key, just the value | ||
*/ | ||
return [lhsExpression.name]; | ||
} | ||
@@ -48,4 +70,4 @@ /** | ||
* ```edge | ||
* @each(user in users) | ||
* {{ user }} {{ $loop.index }} | ||
* @each((user, index) in users) | ||
* {{ user }} {{ index }} | ||
* @endeach | ||
@@ -64,7 +86,10 @@ * ``` | ||
* We just generate the AST and do not transform it, since the transform | ||
* function attempts to resolve identifiers | ||
* function attempts to resolve identifiers and we don't want that | ||
*/ | ||
const parsed = parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename).expression; | ||
utils_1.isSubsetOf(parsed, [edge_parser_1.expressions.BinaryExpression], () => { | ||
utils_1.unallowedExpression(`"${token.properties.jsArg}" is not a valid binary expression for the @each tag`, parsed, token.filename); | ||
const { expression } = parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename); | ||
/** | ||
* Each tag only accepts the binary expression or sequence expression. ie `user in users` | ||
*/ | ||
utils_1.isSubsetOf(expression, [edge_parser_1.expressions.BinaryExpression], () => { | ||
utils_1.unallowedExpression(`"${token.properties.jsArg}" is not valid expression for the @each tag`, token.filename, parser.utils.getExpressionLoc(expression)); | ||
}); | ||
@@ -75,11 +100,10 @@ /** | ||
const elseIndex = token.children.findIndex((child) => edge_lexer_1.utils.isTag(child, 'else')); | ||
const elseChild = elseIndex > -1 ? token.children.splice(elseIndex) : []; | ||
const elseChildren = elseIndex > -1 ? token.children.splice(elseIndex) : []; | ||
/** | ||
* Fetching the item,index and list for the each loop | ||
*/ | ||
const [item, index] = getLoopItemAndIndex(parsed.left, token.filename); | ||
const list = getLoopList(parsed.right, parser, token.filename); | ||
const list = getLoopList(expression.right, parser, token.filename); | ||
const [item, index] = getLoopItemAndIndex(expression.left, parser, token.filename); | ||
/** | ||
* If there is an else statement, then wrap the loop | ||
* inside the `if` statement first | ||
* If there is an else statement, then wrap the loop inside the `if` statement first | ||
*/ | ||
@@ -92,23 +116,19 @@ if (elseIndex > -1) { | ||
*/ | ||
buffer.writeStatement(`ctx.loop(${list}, function (${item}, loop) {`, token.filename, token.loc.start.line); | ||
const loopCallbackArgs = (index ? [item, index] : [item]).join(','); | ||
buffer.writeStatement(`ctx.loop(${list}, function (${loopCallbackArgs}) {`, token.filename, token.loc.start.line); | ||
/** | ||
* Start a new context frame. Frame ensures the value inside | ||
* the loop is given priority over top level values. Think | ||
* of it as a Javascript block scope. | ||
* Start a new parser scope. So that all variable resolutions for the `item` | ||
* are pointing to the local variable and not the template `state`. | ||
*/ | ||
buffer.writeExpression('ctx.newFrame()', token.filename, -1); | ||
parser.stack.defineScope(); | ||
parser.stack.defineVariable(item); | ||
index && parser.stack.defineVariable(index); | ||
/** | ||
* Set key and value pair on the context | ||
* Process all children | ||
*/ | ||
buffer.writeExpression(`ctx.setOnFrame('${item}', ${item})`, token.filename, -1); | ||
buffer.writeExpression('ctx.setOnFrame(\'$loop\', loop)', token.filename, -1); | ||
buffer.writeExpression(`ctx.setOnFrame('${index}', loop.key)`, token.filename, -1); | ||
/** | ||
* Process all kids | ||
*/ | ||
token.children.forEach((child) => parser.processToken(child, buffer)); | ||
/** | ||
* Remove the frame | ||
* Clear scope | ||
*/ | ||
buffer.writeExpression('ctx.removeFrame()', token.filename, -1); | ||
parser.stack.clearScope(); | ||
/** | ||
@@ -123,3 +143,3 @@ * Close each loop | ||
if (elseIndex > -1) { | ||
elseChild.forEach((child) => parser.processToken(child, buffer)); | ||
elseChildren.forEach((elseChild) => parser.processToken(elseChild, buffer)); | ||
buffer.writeStatement('}', token.filename, -1); | ||
@@ -133,20 +153,6 @@ } | ||
context.macro('loop', function loop(source, callback) { | ||
let index = 0; | ||
const total = lodash_1.size(source); | ||
lodash_1.each(source, (value, key) => { | ||
const isEven = (index + 1) % 2 === 0; | ||
callback(value, { | ||
key: key, | ||
index: index, | ||
first: index === 0, | ||
isOdd: !isEven, | ||
isEven: isEven, | ||
last: (index + 1 === total), | ||
total: total, | ||
}); | ||
index++; | ||
}); | ||
lodash_foreach_1.default(source, callback); | ||
}); | ||
context.macro('size', lodash_1.size); | ||
context.macro('size', lodash_size_1.default); | ||
}, | ||
}; |
import { TagContract } from '../Contracts'; | ||
/** | ||
* Else if tag is used to define conditional blocks. | ||
* | ||
* ```edge | ||
* @if(username) | ||
* // If | ||
* @elseif(user.username) | ||
* // Else if | ||
* @endif | ||
* ``` | ||
* Else if tag is used to define conditional blocks. We keep `@elseif` tag | ||
* is a inline tag, so that everything between the `if` and the `elseif` | ||
* comes `if` children. | ||
*/ | ||
export declare const elseIfTag: TagContract; |
@@ -14,11 +14,5 @@ "use strict"; | ||
/** | ||
* Else if tag is used to define conditional blocks. | ||
* | ||
* ```edge | ||
* @if(username) | ||
* // If | ||
* @elseif(user.username) | ||
* // Else if | ||
* @endif | ||
* ``` | ||
* Else if tag is used to define conditional blocks. We keep `@elseif` tag | ||
* is a inline tag, so that everything between the `if` and the `elseif` | ||
* comes `if` children. | ||
*/ | ||
@@ -33,8 +27,11 @@ exports.elseIfTag = { | ||
compile(parser, buffer, token) { | ||
const parsed = parser.utils.transformAst(parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename); | ||
const parsed = utils_1.parseJsArg(parser, token); | ||
/** | ||
* Disallow sequence expressions | ||
*/ | ||
utils_1.isNotSubsetOf(parsed, [edge_parser_1.expressions.SequenceExpression], () => { | ||
utils_1.unallowedExpression(`{${token.properties.jsArg}} is not a valid argument type for the @elseif tag`, parsed, token.filename); | ||
utils_1.unallowedExpression(`{${token.properties.jsArg}} is not a valid argument type for the @elseif tag`, token.filename, parser.utils.getExpressionLoc(parsed)); | ||
}); | ||
/** | ||
* Start else block | ||
* Start else if block | ||
*/ | ||
@@ -41,0 +38,0 @@ buffer.writeStatement(`} else if (${parser.utils.stringify(parsed)}) {`, token.filename, token.loc.start.line); |
import { TagContract } from '../Contracts'; | ||
/** | ||
* If tag is used to define conditional blocks. | ||
* | ||
* ```edge | ||
* @if(username) | ||
* @endif | ||
* ``` | ||
*/ | ||
export declare const ifTag: TagContract; |
@@ -15,7 +15,2 @@ "use strict"; | ||
* If tag is used to define conditional blocks. | ||
* | ||
* ```edge | ||
* @if(username) | ||
* @endif | ||
* ``` | ||
*/ | ||
@@ -30,5 +25,8 @@ exports.ifTag = { | ||
compile(parser, buffer, token) { | ||
const parsed = parser.utils.transformAst(parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename); | ||
const parsed = utils_1.parseJsArg(parser, token); | ||
/** | ||
* Disallow sequence expressions | ||
*/ | ||
utils_1.isNotSubsetOf(parsed, [edge_parser_1.expressions.SequenceExpression], () => { | ||
utils_1.unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @if tag`, parsed, token.filename); | ||
utils_1.unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @if tag`, token.filename, parser.utils.getExpressionLoc(parsed)); | ||
}); | ||
@@ -40,3 +38,3 @@ /** | ||
/** | ||
* Process of all kids recursively | ||
* Process of all children recursively | ||
*/ | ||
@@ -43,0 +41,0 @@ token.children.forEach((child) => parser.processToken(child, buffer)); |
@@ -0,3 +1,12 @@ | ||
import { Parser } from 'edge-parser'; | ||
import { TagContract } from '../Contracts'; | ||
/** | ||
* List of expressions allowed for the include tag | ||
*/ | ||
export declare const ALLOWED_EXPRESSION: ("Identifier" | "MemberExpression" | "CallExpression" | "Literal" | "TemplateLiteral" | "ConditionalExpression" | "LogicalExpression")[]; | ||
/** | ||
* Returns the expression for rendering the partial | ||
*/ | ||
export declare function getRenderExpression(parser: Parser, parsedExpression: any): string; | ||
/** | ||
* Include tag is used to include partials in the same scope of the parent | ||
@@ -4,0 +13,0 @@ * template. |
@@ -14,2 +14,37 @@ "use strict"; | ||
/** | ||
* List of expressions allowed for the include tag | ||
*/ | ||
exports.ALLOWED_EXPRESSION = [ | ||
edge_parser_1.expressions.Identifier, | ||
edge_parser_1.expressions.Literal, | ||
edge_parser_1.expressions.LogicalExpression, | ||
edge_parser_1.expressions.MemberExpression, | ||
edge_parser_1.expressions.ConditionalExpression, | ||
edge_parser_1.expressions.CallExpression, | ||
edge_parser_1.expressions.TemplateLiteral, | ||
]; | ||
/** | ||
* Returns the expression for rendering the partial | ||
*/ | ||
function getRenderExpression(parser, parsedExpression) { | ||
/** | ||
* We need to pass the local variables to the partial render function | ||
*/ | ||
const localVariables = parser.stack.list(); | ||
/** | ||
* Arguments for the `renderInline` method | ||
*/ | ||
const renderArgs = localVariables.length | ||
? [parser.utils.stringify(parsedExpression), localVariables.map((localVar) => `"${localVar}"`).join(',')] | ||
: [parser.utils.stringify(parsedExpression)]; | ||
/** | ||
* Arguments for invoking the output function of `renderInline` | ||
*/ | ||
const callFnArgs = localVariables.length | ||
? ['template', 'state', 'ctx', localVariables.map((localVar) => localVar).join(',')] | ||
: ['template', 'state', 'ctx']; | ||
return `template.renderInline(${renderArgs.join(',')})(${callFnArgs.join(',')})`; | ||
} | ||
exports.getRenderExpression = getRenderExpression; | ||
/** | ||
* Include tag is used to include partials in the same scope of the parent | ||
@@ -30,21 +65,11 @@ * template. | ||
compile(parser, buffer, token) { | ||
const parsed = parser.utils.transformAst(parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename); | ||
utils_1.isSubsetOf(parsed, [ | ||
edge_parser_1.expressions.Identifier, | ||
edge_parser_1.expressions.Literal, | ||
edge_parser_1.expressions.LogicalExpression, | ||
edge_parser_1.expressions.MemberExpression, | ||
edge_parser_1.expressions.ConditionalExpression, | ||
edge_parser_1.expressions.CallExpression, | ||
edge_parser_1.expressions.TemplateLiteral, | ||
], () => { | ||
utils_1.unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @include tag`, parsed, token.filename); | ||
}); | ||
const parsed = utils_1.parseJsArg(parser, token); | ||
/** | ||
* Include template. Since the partials can be a runtime value, we cannot inline | ||
* the content right now and have to defer to runtime to get the value of | ||
* the partial and then process it | ||
* Only mentioned expressions are allowed inside `@include` tag | ||
*/ | ||
buffer.outputExpression(`template.renderInline(${parser.utils.stringify(parsed)})(template, ctx)`, token.filename, token.loc.start.line, true); | ||
utils_1.isSubsetOf(parsed, exports.ALLOWED_EXPRESSION, () => { | ||
utils_1.unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @include tag`, token.filename, parser.utils.getExpressionLoc(parsed)); | ||
}); | ||
buffer.outputExpression(getRenderExpression(parser, parsed), token.filename, token.loc.start.line, false); | ||
}, | ||
}; |
@@ -5,2 +5,3 @@ export { ifTag as if } from './If'; | ||
export { includeTag as include } from './Include'; | ||
export { includeIfTag as includeIf } from './IncludeIf'; | ||
export { eachTag as each } from './Each'; | ||
@@ -7,0 +8,0 @@ export { componentTag as component } from './Component'; |
@@ -19,2 +19,4 @@ "use strict"; | ||
exports.include = Include_1.includeTag; | ||
var IncludeIf_1 = require("./IncludeIf"); | ||
exports.includeIf = IncludeIf_1.includeIfTag; | ||
var Each_1 = require("./Each"); | ||
@@ -21,0 +23,0 @@ exports.each = Each_1.eachTag; |
@@ -40,3 +40,3 @@ "use strict"; | ||
compile(parser, buffer, token) { | ||
const parsed = parser.utils.transformAst(parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename); | ||
const parsed = utils_1.parseJsArg(parser, token); | ||
/** | ||
@@ -46,3 +46,3 @@ * The set tag only accepts a sequence expression. | ||
utils_1.isSubsetOf(parsed, [edge_parser_1.expressions.SequenceExpression], () => { | ||
throw utils_1.unallowedExpression(`"${token.properties.jsArg}" is not a valid key-value pair for the @slot tag`, parsed, token.filename); | ||
throw utils_1.unallowedExpression(`"${token.properties.jsArg}" is not a valid key-value pair for the @slot tag`, token.filename, parser.utils.getExpressionLoc(parsed)); | ||
}); | ||
@@ -52,4 +52,4 @@ /** | ||
*/ | ||
if (parsed.expressions.length > 3) { | ||
throw new edge_error_1.EdgeError('maximum of 3 arguments are allowed for the @set tag', 'E_MAX_ARGUMENTS', { | ||
if (parsed.expressions.length > 2) { | ||
throw new edge_error_1.EdgeError('maximum of 2 arguments are allowed for the @set tag', 'E_MAX_ARGUMENTS', { | ||
line: parsed.loc.start.line, | ||
@@ -60,3 +60,3 @@ col: parsed.loc.start.column, | ||
} | ||
const [key, value, isolated] = parsed.expressions; | ||
const [key, value] = parsed.expressions; | ||
/** | ||
@@ -66,9 +66,16 @@ * The key has to be a literal value | ||
utils_1.isSubsetOf(key, [edge_parser_1.expressions.Literal], () => { | ||
throw utils_1.unallowedExpression('The first argument for @set tag must be a string literal', parsed, token.filename); | ||
throw utils_1.unallowedExpression('The first argument for @set tag must be a string literal', token.filename, parser.utils.getExpressionLoc(key)); | ||
}); | ||
/** | ||
* Write statement to mutate the key | ||
* Write statement to mutate the key. If the variable has already been | ||
* defined, then just update it's value. | ||
* | ||
* We do not allow re-declaring a variable as of now | ||
*/ | ||
buffer.writeExpression(`ctx.set(${key.raw}, ${parser.utils.stringify(value)}, ${!!isolated})`, token.filename, token.loc.start.line); | ||
const expression = parser.stack.has(key.value) | ||
? `${key.value} = ${parser.utils.stringify(value)}` | ||
: `let ${key.value} = ${parser.utils.stringify(value)}`; | ||
buffer.writeExpression(expression, token.filename, token.loc.start.line); | ||
parser.stack.defineVariable(key.value); | ||
}, | ||
}; |
@@ -31,5 +31,8 @@ "use strict"; | ||
compile(parser, buffer, token) { | ||
const parsed = parser.utils.transformAst(parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename); | ||
const parsed = utils_1.parseJsArg(parser, token); | ||
/** | ||
* Disallow sequence expressions | ||
*/ | ||
utils_1.isNotSubsetOf(parsed, [edge_parser_1.expressions.SequenceExpression], () => { | ||
utils_1.unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @unless tag`, parsed, token.filename); | ||
utils_1.unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @unless tag`, token.filename, parser.utils.getExpressionLoc(parsed)); | ||
}); | ||
@@ -41,3 +44,3 @@ /** | ||
/** | ||
* Process of all kids recursively | ||
* Process of all children recursively | ||
*/ | ||
@@ -44,0 +47,0 @@ token.children.forEach((child) => parser.processToken(child, buffer)); |
@@ -40,11 +40,26 @@ "use strict"; | ||
compile(parser, buffer, token) { | ||
/** | ||
* Holding the yield variable counder on the buffer as a private | ||
* variable | ||
*/ | ||
let yieldCounter = buffer['yieldCounter'] || 0; | ||
buffer['yieldCounter'] = yieldCounter++; | ||
const parsed = parser.utils.transformAst(parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename); | ||
const parsed = utils_1.parseJsArg(parser, token); | ||
/** | ||
* Sequence expression is not | ||
*/ | ||
utils_1.isNotSubsetOf(parsed, [edge_parser_1.expressions.SequenceExpression], () => { | ||
utils_1.unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @yield tag`, parsed, token.filename); | ||
utils_1.unallowedExpression(`"${token.properties.jsArg}" is not a valid argument type for the @yield tag`, token.filename, parser.utils.getExpressionLoc(parsed)); | ||
}); | ||
const parsedString = parser.utils.stringify(parsed); | ||
/** | ||
* Write main content when it's truthy | ||
* Write main content when it's truthy. The reason we store a reference to a variable first, is that | ||
* at times the properties can have side-effects, so calling it inside `if` and then yield may | ||
* cause unintended behavior. For example: | ||
* | ||
* `@yield(getPropertyAge())` | ||
* | ||
* The `getPropertyAge` uses timestamp comparsion for some logic. So if we will call this method | ||
* twice, first inside the `if` block and then to yield it, then it may cause some unintended | ||
* behavior. | ||
*/ | ||
@@ -55,3 +70,3 @@ buffer.writeExpression(`let yield_${yieldCounter} = ${parsedString}`, token.filename, token.loc.start.line); | ||
/** | ||
* Else write fallback | ||
* Write fallback content | ||
*/ | ||
@@ -58,0 +73,0 @@ if (!token.properties.selfclosed) { |
@@ -16,2 +16,10 @@ import { CompilerContract } from '../Contracts'; | ||
/** | ||
* Wraps template to a function | ||
*/ | ||
private wrapToFunction; | ||
/** | ||
* Trims top and bottom new lines from the content | ||
*/ | ||
trimTopBottomNewLines(value: any): any; | ||
/** | ||
* Render the template inline by sharing the state of the current template. | ||
@@ -23,6 +31,6 @@ * | ||
* // render and use output | ||
* partialFn(template, ctx) | ||
* partialFn(template, state, ctx) | ||
* ``` | ||
*/ | ||
renderInline(templatePath: string): Function; | ||
renderInline(templatePath: string, ...localVariables: string[]): Function; | ||
/** | ||
@@ -29,0 +37,0 @@ * Renders the template with custom state. The `sharedState` of the template is still |
@@ -10,6 +10,8 @@ "use strict"; | ||
*/ | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const lodash_1 = require("lodash"); | ||
const lodash_merge_1 = __importDefault(require("lodash.merge")); | ||
const Context_1 = require("../Context"); | ||
const Presenter_1 = require("../Presenter"); | ||
/** | ||
@@ -23,5 +25,18 @@ * The template is used to compile and run templates. Also the instance | ||
this.compiler = compiler; | ||
this.sharedState = lodash_1.merge({}, globals, locals); | ||
this.sharedState = lodash_merge_1.default({}, globals, locals); | ||
} | ||
/** | ||
* Wraps template to a function | ||
*/ | ||
wrapToFunction(template, ...localVariables) { | ||
const args = ['template', 'state', 'ctx'].concat(localVariables); | ||
return new Function('', `return function template (${args.join(',')}) { ${template} }`)(); | ||
} | ||
/** | ||
* Trims top and bottom new lines from the content | ||
*/ | ||
trimTopBottomNewLines(value) { | ||
return value.replace(/^\n|^\r\n/, '').replace(/\n$|\r\n$/, ''); | ||
} | ||
/** | ||
* Render the template inline by sharing the state of the current template. | ||
@@ -33,7 +48,8 @@ * | ||
* // render and use output | ||
* partialFn(template, ctx) | ||
* partialFn(template, state, ctx) | ||
* ``` | ||
*/ | ||
renderInline(templatePath) { | ||
return new Function('template', 'ctx', this.compiler.compile(templatePath, true).template); | ||
renderInline(templatePath, ...localVariables) { | ||
const { template: compiledTemplate } = this.compiler.compile(templatePath, localVariables); | ||
return this.wrapToFunction(compiledTemplate, ...localVariables); | ||
} | ||
@@ -52,6 +68,5 @@ /** | ||
renderWithState(template, state, slots) { | ||
const { template: compiledTemplate, Presenter } = this.compiler.compile(template, false); | ||
const presenter = new (Presenter || Presenter_1.Presenter)(lodash_1.merge(state, { $slots: slots }), this.sharedState); | ||
const ctx = new Context_1.Context(presenter); | ||
return new Function('template', 'ctx', compiledTemplate)(this, ctx); | ||
const { template: compiledTemplate } = this.compiler.compile(template); | ||
const templateState = Object.assign({}, this.sharedState, state, { $slots: slots }); | ||
return this.wrapToFunction(compiledTemplate)(this, templateState, new Context_1.Context()); | ||
} | ||
@@ -66,8 +81,8 @@ /** | ||
render(template, state) { | ||
const { template: compiledTemplate, Presenter } = this.compiler.compile(template, false); | ||
const presenter = new (Presenter || Presenter_1.Presenter)(state, this.sharedState); | ||
const ctx = new Context_1.Context(presenter); | ||
return new Function('template', 'ctx', compiledTemplate)(this, ctx); | ||
const { template: compiledTemplate } = this.compiler.compile(template); | ||
const templateState = Object.assign({}, this.sharedState, state); | ||
const fn = this.wrapToFunction(compiledTemplate); | ||
return this.trimTopBottomNewLines(fn(this, templateState, new Context_1.Context())); | ||
} | ||
} | ||
exports.Template = Template; |
@@ -1,2 +0,3 @@ | ||
import { expressions as expressionsList } from 'edge-parser'; | ||
import { TagToken } from 'edge-lexer'; | ||
import { expressions as expressionsList, Parser } from 'edge-parser'; | ||
declare type ExpressionList = readonly (keyof typeof expressionsList)[]; | ||
@@ -7,3 +8,6 @@ /** | ||
*/ | ||
export declare function unallowedExpression(message: string, expression: any, filename: string): void; | ||
export declare function unallowedExpression(message: string, filename: string, loc: { | ||
line: number; | ||
col: number; | ||
}): void; | ||
/** | ||
@@ -31,2 +35,6 @@ * Validates the expression type to be part of the allowed | ||
export declare function isNotSubsetOf(expression: any, expressions: ExpressionList, errorCallback: () => void): void; | ||
/** | ||
* Parses the jsArg by generating and transforming its AST | ||
*/ | ||
export declare function parseJsArg(parser: Parser, token: TagToken): any; | ||
export {}; |
@@ -16,6 +16,6 @@ "use strict"; | ||
*/ | ||
function unallowedExpression(message, expression, filename) { | ||
function unallowedExpression(message, filename, loc) { | ||
throw new edge_error_1.EdgeError(message, 'E_UNALLOWED_EXPRESSION', { | ||
line: expression.loc.start.line, | ||
col: expression.loc.start.column, | ||
line: loc.line, | ||
col: loc.col, | ||
filename: filename, | ||
@@ -57,1 +57,8 @@ }); | ||
exports.isNotSubsetOf = isNotSubsetOf; | ||
/** | ||
* Parses the jsArg by generating and transforming its AST | ||
*/ | ||
function parseJsArg(parser, token) { | ||
return parser.utils.transformAst(parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename, parser); | ||
} | ||
exports.parseJsArg = parseJsArg; |
{ | ||
"name": "edge.js", | ||
"version": "2.2.0", | ||
"version": "3.0.0", | ||
"description": "Template engine", | ||
@@ -35,7 +35,6 @@ "main": "build/index.js", | ||
"devDependencies": { | ||
"@adonisjs/mrm-preset": "^2.2.4", | ||
"@poppinss/dev-utils": "^1.0.4", | ||
"@types/lodash": "^4.14.149", | ||
"@types/node": "^13.9.0", | ||
"commitizen": "^4.0.3", | ||
"@adonisjs/mrm-preset": "^2.3.0", | ||
"@poppinss/dev-utils": "^1.0.6", | ||
"@types/node": "^13.11.1", | ||
"commitizen": "^4.0.4", | ||
"cz-conventional-changelog": "^3.1.0", | ||
@@ -46,15 +45,10 @@ "dedent-js": "^1.0.1", | ||
"eslint": "^6.8.0", | ||
"eslint-plugin-adonis": "^1.0.8", | ||
"fs-extra": "^8.1.0", | ||
"husky": "^4.2.3", | ||
"eslint-plugin-adonis": "^1.0.9", | ||
"husky": "^4.2.5", | ||
"japa": "^3.0.1", | ||
"mrm": "^2.1.0", | ||
"np": "^5.2.1", | ||
"ts-node": "^8.6.2", | ||
"typedoc": "^0.16.11", | ||
"typedoc-clarity-theme": "^1.1.0", | ||
"typedoc-plugin-external-module-name": "^3.0.0", | ||
"typescript": "^3.8.3", | ||
"yorkie": "^2.0.0", | ||
"youch": "^2.0.10" | ||
"js-stringify": "^1.0.2", | ||
"mrm": "^2.2.1", | ||
"np": "^6.2.1", | ||
"ts-node": "^8.8.2", | ||
"typescript": "^3.8.3" | ||
}, | ||
@@ -75,14 +69,12 @@ "config": { | ||
"dependencies": { | ||
"@poppinss/utils": "^2.1.2", | ||
"edge-error": "^1.0.4", | ||
"edge-lexer": "^2.1.0", | ||
"edge-parser": "^3.0.8", | ||
"edge-lexer": "^3.0.3", | ||
"edge-parser": "^5.0.6", | ||
"he": "^1.2.0", | ||
"import-fresh": "^3.2.1", | ||
"lodash": "^4.17.15", | ||
"macroable": "^4.0.2" | ||
"lodash.foreach": "^4.5.0", | ||
"lodash.merge": "^4.6.2", | ||
"lodash.size": "^4.2.0", | ||
"macroable": "^4.0.4", | ||
"truncatise": "0.0.8" | ||
}, | ||
"gitHooks": { | ||
"commit-msg": "node ./node_modules/@adonisjs/mrm-preset/validateCommit/conventional/validate.js" | ||
}, | ||
"husky": { | ||
@@ -89,0 +81,0 @@ "hooks": { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
17
62
1
105992
9
2968
+ Addedlodash.foreach@^4.5.0
+ Addedlodash.merge@^4.6.2
+ Addedlodash.size@^4.2.0
+ Addedtruncatise@0.0.8
+ Addedacorn@8.12.1(transitive)
+ Addededge-lexer@3.2.2(transitive)
+ Addededge-parser@5.4.0(transitive)
+ Addedlodash.foreach@4.5.0(transitive)
+ Addedlodash.merge@4.6.2(transitive)
+ Addedlodash.size@4.2.0(transitive)
+ Addedtruncatise@0.0.8(transitive)
- Removed@poppinss/utils@^2.1.2
- Removedimport-fresh@^3.2.1
- Removedlodash@^4.17.15
- Removed@poppinss/utils@2.5.10(transitive)
- Removedacorn@7.4.1(transitive)
- Removedbuffer-alloc@1.2.0(transitive)
- Removedbuffer-alloc-unsafe@1.1.0(transitive)
- Removedbuffer-fill@1.0.0(transitive)
- Removedcallsites@3.1.0(transitive)
- Removededge-lexer@2.2.0(transitive)
- Removededge-parser@3.0.8(transitive)
- Removedfast-safe-stringify@2.1.1(transitive)
- Removedfs-readdir-recursive@1.1.0(transitive)
- Removedimport-fresh@3.3.0(transitive)
- Removedklona@2.0.6(transitive)
- Removedlodash@4.17.21(transitive)
- Removedms@2.1.3(transitive)
- Removedparent-module@1.0.1(transitive)
- Removedrequire-all@3.0.0(transitive)
- Removedresolve-from@4.0.05.0.0(transitive)
Updatededge-lexer@^3.0.3
Updatededge-parser@^5.0.6
Updatedmacroable@^4.0.4