edge-lexer
Advanced tools
Comparing version 2.0.10 to 2.0.11
@@ -0,2 +1,13 @@ | ||
/** | ||
* @module lexer | ||
*/ | ||
/** | ||
* edge-lexer | ||
* | ||
* (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. | ||
*/ | ||
export { Tokenizer } from './src/Tokenizer/index'; | ||
export { Tags, TagToken, MustacheToken, NewLineToken, RawToken, Token, LexerTagDefinitionContract, MustacheTypes, TagTypes, TagProps, MustacheProps, } from './src/Contracts/index'; |
"use strict"; | ||
/** | ||
* @module lexer | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* edge-lexer | ||
* | ||
* (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 index_1 = require("./src/Tokenizer/index"); | ||
@@ -4,0 +15,0 @@ exports.Tokenizer = index_1.Tokenizer; |
@@ -0,1 +1,15 @@ | ||
/** | ||
* @module lexer | ||
*/ | ||
/** | ||
* edge-lexer | ||
* | ||
* (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. | ||
*/ | ||
/** | ||
* Types for mustache statements | ||
*/ | ||
export declare enum MustacheTypes { | ||
@@ -7,2 +21,6 @@ SMUSTACHE = "s__mustache", | ||
} | ||
/** | ||
* The type of node types. Each token | ||
* will have one of these types | ||
*/ | ||
export declare enum TagTypes { | ||
@@ -12,2 +30,5 @@ TAG = "tag", | ||
} | ||
/** | ||
* Properties node for a tag | ||
*/ | ||
export declare type TagProps = { | ||
@@ -18,5 +39,11 @@ name: string; | ||
}; | ||
/** | ||
* Properties for a mustache block | ||
*/ | ||
export declare type MustacheProps = { | ||
jsArg: string; | ||
}; | ||
/** | ||
* Location node for tags and mustache braces | ||
*/ | ||
export declare type LexerLoc = { | ||
@@ -32,2 +59,6 @@ start: { | ||
}; | ||
/** | ||
* The properties required by the lexer on a tag | ||
* definition | ||
*/ | ||
export interface LexerTagDefinitionContract { | ||
@@ -37,2 +68,5 @@ block: boolean; | ||
} | ||
/** | ||
* Raw line token | ||
*/ | ||
export declare type RawToken = { | ||
@@ -43,2 +77,5 @@ type: 'raw'; | ||
}; | ||
/** | ||
* New line token | ||
*/ | ||
export declare type NewLineToken = { | ||
@@ -48,2 +85,5 @@ type: 'newline'; | ||
}; | ||
/** | ||
* Mustache token | ||
*/ | ||
export declare type MustacheToken = { | ||
@@ -54,2 +94,5 @@ type: MustacheTypes; | ||
}; | ||
/** | ||
* Tag token | ||
*/ | ||
export declare type TagToken = { | ||
@@ -62,2 +105,5 @@ type: TagTypes; | ||
export declare type Token = RawToken | NewLineToken | TagToken | MustacheToken; | ||
/** | ||
* The runtime tag node to know the shape of a tag | ||
*/ | ||
export declare type RuntimeTag = { | ||
@@ -73,2 +119,5 @@ name: string; | ||
}; | ||
/** | ||
* Runtime mustache node to know the shape of the mustache | ||
*/ | ||
export declare type RuntimeMustache = { | ||
@@ -75,0 +124,0 @@ escaped: boolean; |
"use strict"; | ||
/** | ||
* @module lexer | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* edge-lexer | ||
* | ||
* (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. | ||
*/ | ||
/** | ||
* Types for mustache statements | ||
*/ | ||
var MustacheTypes; | ||
@@ -10,2 +24,6 @@ (function (MustacheTypes) { | ||
})(MustacheTypes = exports.MustacheTypes || (exports.MustacheTypes = {})); | ||
/** | ||
* The type of node types. Each token | ||
* will have one of these types | ||
*/ | ||
var TagTypes; | ||
@@ -12,0 +30,0 @@ (function (TagTypes) { |
@@ -0,3 +1,20 @@ | ||
/** | ||
* @module lexer | ||
*/ | ||
/** | ||
* edge-lexer | ||
* | ||
* (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. | ||
*/ | ||
import { RuntimeTag, RuntimeMustache, Tags } from '../Contracts'; | ||
/** | ||
* Returns runtime tag node if tag is detected and is a registered tag | ||
*/ | ||
export declare function getTag(content: string, line: number, col: number, tags: Tags): RuntimeTag | null; | ||
/** | ||
* Returns the runtime mustache node if mustache is detected | ||
*/ | ||
export declare function getMustache(content: string, line: number, col: number): RuntimeMustache | null; |
"use strict"; | ||
/** | ||
* @module lexer | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* The only regex we need in the entire lexer. Also tested | ||
* with https://github.com/substack/safe-regex | ||
*/ | ||
const TAG_REGEX = /^(\s*)(@{1,2})(!)?(\w+)(\s{0,2})/; | ||
/** | ||
* Returns runtime tag node if tag is detected and is a registered tag | ||
*/ | ||
function getTag(content, line, col, tags) { | ||
const match = TAG_REGEX.exec(content); | ||
/** | ||
* Return when their is no match | ||
*/ | ||
if (!match) { | ||
@@ -11,2 +24,5 @@ return null; | ||
const tag = tags[name]; | ||
/** | ||
* Return when not a registered tag | ||
*/ | ||
if (!tag) { | ||
@@ -21,2 +37,5 @@ return null; | ||
const block = tag.block; | ||
/** | ||
* Advanced the col position | ||
*/ | ||
col += whitespaceLeft + match[2].length + name.length + whitespaceRight; | ||
@@ -26,2 +45,5 @@ if (selfclosed) { | ||
} | ||
/** | ||
* Seekable tags without the brace in same line are invalid | ||
*/ | ||
const hasBrace = seekable && content[col] === '('; | ||
@@ -40,2 +62,5 @@ return { | ||
exports.getTag = getTag; | ||
/** | ||
* Returns the runtime mustache node if mustache is detected | ||
*/ | ||
function getMustache(content, line, col) { | ||
@@ -42,0 +67,0 @@ const mustacheIndex = content.indexOf('{{'); |
@@ -0,2 +1,29 @@ | ||
/** | ||
* @module lexer | ||
*/ | ||
/** | ||
* edge-lexer | ||
* | ||
* (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. | ||
*/ | ||
import { EdgeError } from 'edge-error'; | ||
/** | ||
* Raised when there is inline content next to a tag opening | ||
* block. For example: | ||
* | ||
* Incorrect | ||
* ``` | ||
* @if(username) Hello {{ username }} @endif | ||
* ``` | ||
* | ||
* Correct | ||
* ``` | ||
* @if(username) | ||
* Hello {{ username }} | ||
* @endif | ||
* ``` | ||
*/ | ||
export declare function cannotSeekStatement(chars: string, pos: { | ||
@@ -6,2 +33,15 @@ line: number; | ||
}, filename: string): EdgeError; | ||
/** | ||
* Raised when a tag opening body doesn't have a closing brace. For example: | ||
* | ||
* Incorrect | ||
* ``` | ||
* @if(username | ||
* ``` | ||
* | ||
* Correct | ||
* ``` | ||
* @if(username) | ||
* ``` | ||
*/ | ||
export declare function unclosedParen(pos: { | ||
@@ -11,2 +51,15 @@ line: number; | ||
}, filename: string): EdgeError; | ||
/** | ||
* Raised when a tag is used without an opening brace. For example: | ||
* | ||
* Incorrect | ||
* ``` | ||
* @if username | ||
* ``` | ||
* | ||
* Correct | ||
* ``` | ||
* @if(username) | ||
* ``` | ||
*/ | ||
export declare function unopenedParen(pos: { | ||
@@ -16,2 +69,17 @@ line: number; | ||
}, filename: string): EdgeError; | ||
/** | ||
* Raised when the curly closing brace is missing from the mustache | ||
* statement. For example: | ||
* | ||
* Incorrect | ||
* ``` | ||
* {{ username } | ||
* ``` | ||
* | ||
* Correct | ||
* | ||
* ``` | ||
* {{ username }} | ||
* ``` | ||
*/ | ||
export declare function unclosedCurlyBrace(pos: { | ||
@@ -21,2 +89,16 @@ line: number; | ||
}, filename: string): EdgeError; | ||
/** | ||
* Raised when a block level tag is opened but never closed. For example: | ||
* | ||
* Incorrect | ||
* ``` | ||
* @if(username) | ||
* ``` | ||
* | ||
* Correct | ||
* ``` | ||
* @if(username) | ||
* @endif | ||
* ``` | ||
*/ | ||
export declare function unclosedTag(tag: string, pos: { | ||
@@ -23,0 +105,0 @@ line: number; |
"use strict"; | ||
/** | ||
* @module lexer | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* edge-lexer | ||
* | ||
* (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. | ||
*/ | ||
const edge_error_1 = require("edge-error"); | ||
/** | ||
* Raised when there is inline content next to a tag opening | ||
* block. For example: | ||
* | ||
* Incorrect | ||
* ``` | ||
* @if(username) Hello {{ username }} @endif | ||
* ``` | ||
* | ||
* Correct | ||
* ``` | ||
* @if(username) | ||
* Hello {{ username }} | ||
* @endif | ||
* ``` | ||
*/ | ||
function cannotSeekStatement(chars, pos, filename) { | ||
@@ -12,4 +39,17 @@ return new edge_error_1.EdgeError(`Unexpected token "${chars}"`, 'E_CANNOT_SEEK_STATEMENT', { | ||
exports.cannotSeekStatement = cannotSeekStatement; | ||
/** | ||
* Raised when a tag opening body doesn't have a closing brace. For example: | ||
* | ||
* Incorrect | ||
* ``` | ||
* @if(username | ||
* ``` | ||
* | ||
* Correct | ||
* ``` | ||
* @if(username) | ||
* ``` | ||
*/ | ||
function unclosedParen(pos, filename) { | ||
return new edge_error_1.EdgeError(`Missing token ")"`, 'E_UNCLOSED_PAREN', { | ||
return new edge_error_1.EdgeError('Missing token ")"', 'E_UNCLOSED_PAREN', { | ||
line: pos.line, | ||
@@ -21,4 +61,17 @@ col: pos.col, | ||
exports.unclosedParen = unclosedParen; | ||
/** | ||
* Raised when a tag is used without an opening brace. For example: | ||
* | ||
* Incorrect | ||
* ``` | ||
* @if username | ||
* ``` | ||
* | ||
* Correct | ||
* ``` | ||
* @if(username) | ||
* ``` | ||
*/ | ||
function unopenedParen(pos, filename) { | ||
return new edge_error_1.EdgeError(`Missing token "("`, 'E_UNOPENED_PAREN', { | ||
return new edge_error_1.EdgeError('Missing token "("', 'E_UNOPENED_PAREN', { | ||
line: pos.line, | ||
@@ -30,4 +83,19 @@ col: pos.col, | ||
exports.unopenedParen = unopenedParen; | ||
/** | ||
* Raised when the curly closing brace is missing from the mustache | ||
* statement. For example: | ||
* | ||
* Incorrect | ||
* ``` | ||
* {{ username } | ||
* ``` | ||
* | ||
* Correct | ||
* | ||
* ``` | ||
* {{ username }} | ||
* ``` | ||
*/ | ||
function unclosedCurlyBrace(pos, filename) { | ||
return new edge_error_1.EdgeError(`Missing token "}"`, 'E_UNCLOSED_CURLY_BRACE', { | ||
return new edge_error_1.EdgeError('Missing token "}"', 'E_UNCLOSED_CURLY_BRACE', { | ||
line: pos.line, | ||
@@ -39,2 +107,16 @@ col: pos.col, | ||
exports.unclosedCurlyBrace = unclosedCurlyBrace; | ||
/** | ||
* Raised when a block level tag is opened but never closed. For example: | ||
* | ||
* Incorrect | ||
* ``` | ||
* @if(username) | ||
* ``` | ||
* | ||
* Correct | ||
* ``` | ||
* @if(username) | ||
* @endif | ||
* ``` | ||
*/ | ||
function unclosedTag(tag, pos, filename) { | ||
@@ -41,0 +123,0 @@ return new edge_error_1.EdgeError(`Unclosed tag ${tag}`, 'E_UNCLOSED_TAG', { |
@@ -0,11 +1,58 @@ | ||
/** | ||
* @module lexer | ||
*/ | ||
/** | ||
* edge-lexer | ||
* | ||
* (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. | ||
*/ | ||
/** | ||
* Scan a string and seperate it into 2 pairs. The first pair will be series | ||
* of characters until the ending pattern is found and 2nd pair is the | ||
* left over. | ||
* | ||
* Their are some special behaviors over the regular `string.split` method. | ||
* | ||
* 1. Multiple lines can be passed by calling `scan` method for each line. | ||
* 2. Tolerates characters when they conflict with the ending pattern. | ||
* | ||
* ```js | ||
* const pattern = ')' | ||
* const tolerations = ['(', ')'] | ||
* const scanner = new Scanner(pattern, tolerations) | ||
* | ||
* scanner.scan('2 + 2 * (3))') | ||
* if (scanner.closed) { | ||
* scanner.match // 2 + 2 * (3) | ||
* scanner.leftOver // '' | ||
* } | ||
* ``` | ||
* | ||
* If we take the same string `2 + 2 * (3))` and split it using ')', then we | ||
* will get unexpected result, since the split method splits by finding the | ||
* first match. | ||
*/ | ||
export declare class Scanner { | ||
private _pattern; | ||
private _line; | ||
private _col; | ||
private _tolaretionCounts; | ||
private _tolerateLhs; | ||
private _tolerateRhs; | ||
private _patternLength; | ||
private pattern; | ||
private line; | ||
private col; | ||
private tolaretionCounts; | ||
private tolerateLhs; | ||
private tolerateRhs; | ||
private patternLength; | ||
/** | ||
* Tracking if the scanner has been closed | ||
*/ | ||
closed: boolean; | ||
/** | ||
* The matched content within the pattern | ||
*/ | ||
match: string; | ||
/** | ||
* The content in the same line but after the closing | ||
* of the pattern | ||
*/ | ||
leftOver: string; | ||
@@ -16,5 +63,18 @@ loc: { | ||
}; | ||
constructor(_pattern: string, _toleratePair: [string, string], _line: number, _col: number); | ||
private _matchesPattern; | ||
scan(chunk: any): void; | ||
constructor(pattern: string, toleratePair: [string, string], line: number, col: number); | ||
/** | ||
* Returns a boolean telling if the pattern matches the current | ||
* char and the upcoming chars or not. | ||
* | ||
* This will be used to mark the scanner as closed and stop scanning | ||
* for more chars | ||
*/ | ||
private matchesPattern; | ||
/** | ||
* Scan a string and look for the closing pattern. The string will | ||
* be seperated with the closing pattern and also tracks the | ||
* toleration patterns to make sure they are not making the | ||
* scanner to end due to pattern mis-match. | ||
*/ | ||
scan(chunk: string): void; | ||
} |
"use strict"; | ||
/** | ||
* @module lexer | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* edge-lexer | ||
* | ||
* (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. | ||
*/ | ||
/** | ||
* Scan a string and seperate it into 2 pairs. The first pair will be series | ||
* of characters until the ending pattern is found and 2nd pair is the | ||
* left over. | ||
* | ||
* Their are some special behaviors over the regular `string.split` method. | ||
* | ||
* 1. Multiple lines can be passed by calling `scan` method for each line. | ||
* 2. Tolerates characters when they conflict with the ending pattern. | ||
* | ||
* ```js | ||
* const pattern = ')' | ||
* const tolerations = ['(', ')'] | ||
* const scanner = new Scanner(pattern, tolerations) | ||
* | ||
* scanner.scan('2 + 2 * (3))') | ||
* if (scanner.closed) { | ||
* scanner.match // 2 + 2 * (3) | ||
* scanner.leftOver // '' | ||
* } | ||
* ``` | ||
* | ||
* If we take the same string `2 + 2 * (3))` and split it using ')', then we | ||
* will get unexpected result, since the split method splits by finding the | ||
* first match. | ||
*/ | ||
class Scanner { | ||
constructor(_pattern, _toleratePair, _line, _col) { | ||
this._pattern = _pattern; | ||
this._line = _line; | ||
this._col = _col; | ||
this._tolaretionCounts = 0; | ||
this._tolerateLhs = ''; | ||
this._tolerateRhs = ''; | ||
this._patternLength = this._pattern.length; | ||
constructor(pattern, toleratePair, line, col) { | ||
this.pattern = pattern; | ||
this.line = line; | ||
this.col = col; | ||
this.tolaretionCounts = 0; | ||
this.tolerateLhs = ''; | ||
this.tolerateRhs = ''; | ||
this.patternLength = this.pattern.length; | ||
/** | ||
* Tracking if the scanner has been closed | ||
*/ | ||
this.closed = false; | ||
/** | ||
* The matched content within the pattern | ||
*/ | ||
this.match = ''; | ||
/** | ||
* The content in the same line but after the closing | ||
* of the pattern | ||
*/ | ||
this.leftOver = ''; | ||
this.loc = { | ||
line: this._line, | ||
col: this._col, | ||
line: this.line, | ||
col: this.col, | ||
}; | ||
this._tolerateLhs = _toleratePair[0]; | ||
this._tolerateRhs = _toleratePair[1]; | ||
this.tolerateLhs = toleratePair[0]; | ||
this.tolerateRhs = toleratePair[1]; | ||
} | ||
_matchesPattern(chars, iterationCount) { | ||
for (let i = 0; i < this._patternLength; i++) { | ||
if (this._pattern[i] !== chars[iterationCount + i]) { | ||
/** | ||
* Returns a boolean telling if the pattern matches the current | ||
* char and the upcoming chars or not. | ||
* | ||
* This will be used to mark the scanner as closed and stop scanning | ||
* for more chars | ||
*/ | ||
matchesPattern(chars, iterationCount) { | ||
for (let i = 0; i < this.patternLength; i++) { | ||
if (this.pattern[i] !== chars[iterationCount + i]) { | ||
return false; | ||
@@ -30,2 +84,8 @@ } | ||
} | ||
/** | ||
* Scan a string and look for the closing pattern. The string will | ||
* be seperated with the closing pattern and also tracks the | ||
* toleration patterns to make sure they are not making the | ||
* scanner to end due to pattern mis-match. | ||
*/ | ||
scan(chunk) { | ||
@@ -45,16 +105,36 @@ if (chunk === '\n') { | ||
const char = chunk[iterations]; | ||
if (this._tolaretionCounts === 0 && this._matchesPattern(chunk, iterations)) { | ||
iterations += this._patternLength; | ||
/** | ||
* Toleration count is 0 and closing pattern matches the current | ||
* or series of upcoming characters | ||
*/ | ||
if (this.tolaretionCounts === 0 && this.matchesPattern(chunk, iterations)) { | ||
iterations += this.patternLength; | ||
this.closed = true; | ||
break; | ||
} | ||
if (char === this._tolerateLhs) { | ||
this._tolaretionCounts++; | ||
/** | ||
* Increments the tolarate counts when char is the | ||
* tolerate lhs character | ||
*/ | ||
if (char === this.tolerateLhs) { | ||
this.tolaretionCounts++; | ||
} | ||
if (char === this._tolerateRhs) { | ||
this._tolaretionCounts--; | ||
/** | ||
* Decrements the tolare counts when char is the | ||
* tolerate rhs character | ||
*/ | ||
if (char === this.tolerateRhs) { | ||
this.tolaretionCounts--; | ||
} | ||
/** | ||
* Append to the matched string and waiting for the | ||
* closing pattern | ||
*/ | ||
this.match += char; | ||
iterations++; | ||
} | ||
/** | ||
* If closed, then return the matched string and also the | ||
* left over string | ||
*/ | ||
if (this.closed) { | ||
@@ -61,0 +141,0 @@ this.loc.col += iterations; |
@@ -1,31 +0,131 @@ | ||
import { Tags, Token } from '../Contracts'; | ||
/** | ||
* @module lexer | ||
*/ | ||
import { Scanner } from '../Scanner'; | ||
import { Tags, RuntimeTag, RuntimeMustache, Token } from '../Contracts'; | ||
/** | ||
* Tokenizer converts a bunch of text into an array of tokens. Later | ||
* these tokens can be used to build the transformed text. | ||
* | ||
* Go through the README file to learn more about the syntax and | ||
* the tokens output. | ||
*/ | ||
export declare class Tokenizer { | ||
private _template; | ||
private _tagsDef; | ||
private _options; | ||
private template; | ||
private tagsDef; | ||
private options; | ||
tokens: Token[]; | ||
private _tagStatement; | ||
private _mustacheStatement; | ||
private _line; | ||
private _openedTags; | ||
private _skipNewLine; | ||
constructor(_template: string, _tagsDef: Tags, _options: { | ||
/** | ||
* Holds the current tag statement, until it is closed | ||
*/ | ||
tagStatement: null | { | ||
scanner: Scanner; | ||
tag: RuntimeTag; | ||
}; | ||
/** | ||
* Holds the current tag statement, until it is closed | ||
*/ | ||
mustacheStatement: null | { | ||
scanner: Scanner; | ||
mustache: RuntimeMustache; | ||
}; | ||
/** | ||
* Current line number | ||
*/ | ||
private line; | ||
/** | ||
* An array of opened block level tags | ||
*/ | ||
private openedTags; | ||
/** | ||
* We skip newlines after the opening/closing tags | ||
*/ | ||
private skipNewLine; | ||
constructor(template: string, tagsDef: Tags, options: { | ||
filename: string; | ||
}); | ||
private _getRawNode; | ||
private _getNewLineNode; | ||
private _getTagNode; | ||
private _consumeTag; | ||
private _handleTagOpening; | ||
private _feedCharsToCurrentTag; | ||
private _getMustacheType; | ||
/** | ||
* Returns the raw token | ||
*/ | ||
private getRawNode; | ||
/** | ||
* Returns the new line token | ||
*/ | ||
private getNewLineNode; | ||
/** | ||
* Returns the TagToken for a runtime tag. The `jsArg` and ending | ||
* loc is computed using the scanner and must be passed to this | ||
* method. | ||
*/ | ||
private getTagNode; | ||
/** | ||
* Consume the runtime tag node. | ||
* | ||
* If tag is `block`, then we push it to the list of | ||
* opened tags and wait for the closing statement to | ||
* appear. | ||
* | ||
* Otherwise, we move it to the tokens array directly. | ||
*/ | ||
private consumeTag; | ||
/** | ||
* Handles the opening of the tag. | ||
*/ | ||
private handleTagOpening; | ||
/** | ||
* Scans the string using the scanner and waits for the | ||
* closing brace ')' to appear | ||
*/ | ||
private feedCharsToCurrentTag; | ||
/** | ||
* Returns the mustache type by checking for `safe` and `escaped` | ||
* properties. | ||
*/ | ||
private getMustacheType; | ||
/** | ||
* Returns the mustache token using the runtime mustache node. The `jsArg` and | ||
* ending `loc` is fetched using the scanner. | ||
*/ | ||
private _getMustacheNode; | ||
private _handleMustacheOpening; | ||
private _feedCharsToCurrentMustache; | ||
private _isClosingTag; | ||
private _consumeNode; | ||
private _pushNewLine; | ||
private _processText; | ||
private _checkForErrors; | ||
/** | ||
* Handles the line which has mustache opening braces. | ||
*/ | ||
private handleMustacheOpening; | ||
/** | ||
* Feed chars to the mustache statement, which isn't closed yet. | ||
*/ | ||
private feedCharsToCurrentMustache; | ||
/** | ||
* Returns a boolean telling if the content of the line is the | ||
* closing tag for the most recently opened tag. | ||
* | ||
* The opening and closing has to be in a order, otherwise the | ||
* compiler will get mad. | ||
*/ | ||
private isClosingTag; | ||
/** | ||
* Consume any type of token by moving it to the correct list. If there are | ||
* opened tags, then the token becomes part of the tag children. Otherwise | ||
* moved as top level token. | ||
*/ | ||
private consumeNode; | ||
/** | ||
* Pushes a new line to the list. This method avoids | ||
* new lines at position 0. | ||
*/ | ||
private pushNewLine; | ||
/** | ||
* Process the current line based upon what it is. What it is? | ||
* That's the job of this method to find out. | ||
*/ | ||
private processText; | ||
/** | ||
* Checks for errors after the tokenizer completes it's work, so that we | ||
* can find broken statements or unclosed tags. | ||
*/ | ||
private checkForErrors; | ||
/** | ||
* Parse the template and generate an AST out of it | ||
*/ | ||
parse(): void; | ||
} |
"use strict"; | ||
/** | ||
* @module lexer | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* edge-lexer | ||
* | ||
* (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. | ||
*/ | ||
const Detector_1 = require("../Detector"); | ||
@@ -7,28 +18,61 @@ const Scanner_1 = require("../Scanner"); | ||
const Contracts_1 = require("../Contracts"); | ||
/** | ||
* Tokenizer converts a bunch of text into an array of tokens. Later | ||
* these tokens can be used to build the transformed text. | ||
* | ||
* Go through the README file to learn more about the syntax and | ||
* the tokens output. | ||
*/ | ||
class Tokenizer { | ||
constructor(_template, _tagsDef, _options) { | ||
this._template = _template; | ||
this._tagsDef = _tagsDef; | ||
this._options = _options; | ||
constructor(template, tagsDef, options) { | ||
this.template = template; | ||
this.tagsDef = tagsDef; | ||
this.options = options; | ||
this.tokens = []; | ||
this._tagStatement = null; | ||
this._mustacheStatement = null; | ||
this._line = 0; | ||
this._openedTags = []; | ||
this._skipNewLine = false; | ||
/** | ||
* Holds the current tag statement, until it is closed | ||
*/ | ||
this.tagStatement = null; | ||
/** | ||
* Holds the current tag statement, until it is closed | ||
*/ | ||
this.mustacheStatement = null; | ||
/** | ||
* Current line number | ||
*/ | ||
this.line = 0; | ||
/** | ||
* An array of opened block level tags | ||
*/ | ||
this.openedTags = []; | ||
/** | ||
* We skip newlines after the opening/closing tags | ||
*/ | ||
this.skipNewLine = false; | ||
} | ||
_getRawNode(text) { | ||
/** | ||
* Returns the raw token | ||
*/ | ||
getRawNode(text) { | ||
return { | ||
type: 'raw', | ||
value: text, | ||
line: this._line, | ||
line: this.line, | ||
}; | ||
} | ||
_getNewLineNode() { | ||
/** | ||
* Returns the new line token | ||
*/ | ||
getNewLineNode() { | ||
return { | ||
type: 'newline', | ||
line: this._line - 1, | ||
line: this.line - 1, | ||
}; | ||
} | ||
_getTagNode(tag, jsArg, closingLoc) { | ||
/** | ||
* Returns the TagToken for a runtime tag. The `jsArg` and ending | ||
* loc is computed using the scanner and must be passed to this | ||
* method. | ||
*/ | ||
getTagNode(tag, jsArg, closingLoc) { | ||
return { | ||
@@ -51,38 +95,85 @@ type: tag.escaped ? Contracts_1.TagTypes.ETAG : Contracts_1.TagTypes.TAG, | ||
} | ||
_consumeTag(tag, jsArg, loc) { | ||
/** | ||
* Consume the runtime tag node. | ||
* | ||
* If tag is `block`, then we push it to the list of | ||
* opened tags and wait for the closing statement to | ||
* appear. | ||
* | ||
* Otherwise, we move it to the tokens array directly. | ||
*/ | ||
consumeTag(tag, jsArg, loc) { | ||
if (tag.block && !tag.selfclosed) { | ||
this._openedTags.push(this._getTagNode(tag, jsArg, loc)); | ||
this.openedTags.push(this.getTagNode(tag, jsArg, loc)); | ||
} | ||
else { | ||
this._consumeNode(this._getTagNode(tag, jsArg, loc)); | ||
this.consumeNode(this.getTagNode(tag, jsArg, loc)); | ||
} | ||
} | ||
_handleTagOpening(line, tag) { | ||
/** | ||
* Handles the opening of the tag. | ||
*/ | ||
handleTagOpening(line, tag) { | ||
if (tag.seekable && !tag.hasBrace) { | ||
throw Exceptions_1.unopenedParen({ line: tag.line, col: tag.col }, this._options.filename); | ||
throw Exceptions_1.unopenedParen({ line: tag.line, col: tag.col }, this.options.filename); | ||
} | ||
/** | ||
* When tag is not seekable, then their is no need to create | ||
* a scanner instance, just consume it right away. | ||
*/ | ||
if (!tag.seekable) { | ||
this._consumeTag(tag, '', { line: tag.line, col: tag.col }); | ||
this.consumeTag(tag, '', { line: tag.line, col: tag.col }); | ||
return; | ||
} | ||
/** | ||
* Advance the `col`, since we do not want to start from the | ||
* starting brace `(`. | ||
*/ | ||
tag.col += 1; | ||
this._tagStatement = { | ||
/** | ||
* Create a new block statement with the scanner to find | ||
* the closing brace ')' | ||
*/ | ||
this.tagStatement = { | ||
tag: tag, | ||
scanner: new Scanner_1.Scanner(')', ['(', ')'], this._line, tag.col), | ||
scanner: new Scanner_1.Scanner(')', ['(', ')'], this.line, tag.col), | ||
}; | ||
this._feedCharsToCurrentTag(line.slice(tag.col)); | ||
/** | ||
* Pass all remaining content to the scanner | ||
*/ | ||
this.feedCharsToCurrentTag(line.slice(tag.col)); | ||
} | ||
_feedCharsToCurrentTag(content) { | ||
const { tag, scanner } = this._tagStatement; | ||
/** | ||
* Scans the string using the scanner and waits for the | ||
* closing brace ')' to appear | ||
*/ | ||
feedCharsToCurrentTag(content) { | ||
const { tag, scanner } = this.tagStatement; | ||
scanner.scan(content); | ||
/** | ||
* If scanner is not closed, then we need to keep on | ||
* feeding more content | ||
*/ | ||
if (!scanner.closed) { | ||
return; | ||
} | ||
this._consumeTag(tag, scanner.match, scanner.loc); | ||
/** | ||
* Consume the tag once we have found the closing brace and set | ||
* block statement to null | ||
*/ | ||
this.consumeTag(tag, scanner.match, scanner.loc); | ||
/** | ||
* Raise error, if there is inline content after the closing brace ')' | ||
* `@if(username) hello {{ username }}` is invalid | ||
*/ | ||
if (scanner.leftOver.trim()) { | ||
throw Exceptions_1.cannotSeekStatement(scanner.leftOver, scanner.loc, this._options.filename); | ||
throw Exceptions_1.cannotSeekStatement(scanner.leftOver, scanner.loc, this.options.filename); | ||
} | ||
this._tagStatement = null; | ||
this.tagStatement = null; | ||
} | ||
_getMustacheType(mustache) { | ||
/** | ||
* Returns the mustache type by checking for `safe` and `escaped` | ||
* properties. | ||
*/ | ||
getMustacheType(mustache) { | ||
if (mustache.safe) { | ||
@@ -93,5 +184,9 @@ return mustache.escaped ? Contracts_1.MustacheTypes.ESMUSTACHE : Contracts_1.MustacheTypes.SMUSTACHE; | ||
} | ||
/** | ||
* Returns the mustache token using the runtime mustache node. The `jsArg` and | ||
* ending `loc` is fetched using the scanner. | ||
*/ | ||
_getMustacheNode(mustache, jsArg, closingLoc) { | ||
return { | ||
type: this._getMustacheType(mustache), | ||
type: this.getMustacheType(mustache), | ||
properties: { | ||
@@ -109,43 +204,102 @@ jsArg: jsArg, | ||
} | ||
_handleMustacheOpening(line, mustache) { | ||
/** | ||
* Handles the line which has mustache opening braces. | ||
*/ | ||
handleMustacheOpening(line, mustache) { | ||
const pattern = mustache.safe ? '}}}' : '}}'; | ||
const textLeftIndex = mustache.escaped ? mustache.realCol - 1 : mustache.realCol; | ||
/** | ||
* Pull everything that is on the left of the mustache | ||
* statement and use it as a raw node | ||
*/ | ||
if (textLeftIndex > 0) { | ||
this._consumeNode(this._getRawNode(line.slice(0, textLeftIndex))); | ||
this.consumeNode(this.getRawNode(line.slice(0, textLeftIndex))); | ||
} | ||
/** | ||
* Skip the curly braces when reading the expression inside | ||
* it. We are actually skipping opening curly braces | ||
* `{{`, however, their length will be same as the | ||
* closing one's/ | ||
*/ | ||
mustache.col += pattern.length; | ||
mustache.realCol += pattern.length; | ||
this._mustacheStatement = { | ||
/** | ||
* Create a new mustache statement with a scanner to scan for | ||
* closing mustache braces. Note the closing `pattern` is | ||
* different for safe and normal mustache. | ||
*/ | ||
this.mustacheStatement = { | ||
mustache, | ||
scanner: new Scanner_1.Scanner(pattern, ['{', '}'], mustache.line, mustache.col), | ||
}; | ||
this._feedCharsToCurrentMustache(line.slice(mustache.realCol)); | ||
/** | ||
* Feed text to the mustache statement and wait for the closing braces | ||
*/ | ||
this.feedCharsToCurrentMustache(line.slice(mustache.realCol)); | ||
} | ||
_feedCharsToCurrentMustache(content) { | ||
const { mustache, scanner } = this._mustacheStatement; | ||
/** | ||
* Feed chars to the mustache statement, which isn't closed yet. | ||
*/ | ||
feedCharsToCurrentMustache(content) { | ||
const { mustache, scanner } = this.mustacheStatement; | ||
scanner.scan(content); | ||
/** | ||
* If scanner is not closed, then return early, since their | ||
* not much we can do here. | ||
*/ | ||
if (!scanner.closed) { | ||
return; | ||
} | ||
this._consumeNode(this._getMustacheNode(mustache, scanner.match, scanner.loc)); | ||
/** | ||
* Consume the node as soon as we have found the closing brace | ||
*/ | ||
this.consumeNode(this._getMustacheNode(mustache, scanner.match, scanner.loc)); | ||
/** | ||
* If their is leftOver text after the mustache closing brace, then re-scan | ||
* it for more mustache statements. Example: | ||
* | ||
* I following statement, `, and {{ age }}` is the left over. | ||
* ``` | ||
* {{ username }}, and {{ age }} | ||
* ``` | ||
* | ||
* This block is same the generic new line handler method. However, their is | ||
* no need to check for tags and comments, so we ditch that method and | ||
* process it here by duplicating code (which is fine). | ||
*/ | ||
if (scanner.leftOver.trim()) { | ||
const anotherMustache = Detector_1.getMustache(scanner.leftOver, scanner.loc.line, scanner.loc.col); | ||
if (anotherMustache) { | ||
this._handleMustacheOpening(scanner.leftOver, anotherMustache); | ||
this.handleMustacheOpening(scanner.leftOver, anotherMustache); | ||
return; | ||
} | ||
this._consumeNode(this._getRawNode(scanner.leftOver)); | ||
this.consumeNode(this.getRawNode(scanner.leftOver)); | ||
} | ||
this._mustacheStatement = null; | ||
/** | ||
* Set mustache statement to null | ||
*/ | ||
this.mustacheStatement = null; | ||
} | ||
_isClosingTag(line) { | ||
if (!this._openedTags.length) { | ||
/** | ||
* Returns a boolean telling if the content of the line is the | ||
* closing tag for the most recently opened tag. | ||
* | ||
* The opening and closing has to be in a order, otherwise the | ||
* compiler will get mad. | ||
*/ | ||
isClosingTag(line) { | ||
if (!this.openedTags.length) { | ||
return false; | ||
} | ||
const recentTag = this._openedTags[this._openedTags.length - 1]; | ||
const recentTag = this.openedTags[this.openedTags.length - 1]; | ||
return line.trim() === `@end${recentTag.properties.name}`; | ||
} | ||
_consumeNode(tag) { | ||
if (this._openedTags.length) { | ||
this._openedTags[this._openedTags.length - 1].children.push(tag); | ||
/** | ||
* Consume any type of token by moving it to the correct list. If there are | ||
* opened tags, then the token becomes part of the tag children. Otherwise | ||
* moved as top level token. | ||
*/ | ||
consumeNode(tag) { | ||
if (this.openedTags.length) { | ||
this.openedTags[this.openedTags.length - 1].children.push(tag); | ||
return; | ||
@@ -155,65 +309,117 @@ } | ||
} | ||
_pushNewLine() { | ||
if (this._line === 1) { | ||
/** | ||
* Pushes a new line to the list. This method avoids | ||
* new lines at position 0. | ||
*/ | ||
pushNewLine() { | ||
if (this.line === 1) { | ||
return; | ||
} | ||
this._consumeNode(this._getNewLineNode()); | ||
this.consumeNode(this.getNewLineNode()); | ||
} | ||
_processText(line) { | ||
if (this._tagStatement) { | ||
this._feedCharsToCurrentTag('\n'); | ||
this._feedCharsToCurrentTag(line); | ||
/** | ||
* Process the current line based upon what it is. What it is? | ||
* That's the job of this method to find out. | ||
*/ | ||
processText(line) { | ||
/** | ||
* There is an open block statement, so feed line to it | ||
*/ | ||
if (this.tagStatement) { | ||
this.feedCharsToCurrentTag('\n'); | ||
this.feedCharsToCurrentTag(line); | ||
return; | ||
} | ||
if (this._mustacheStatement) { | ||
this._feedCharsToCurrentMustache('\n'); | ||
this._feedCharsToCurrentMustache(line); | ||
/** | ||
* There is an open mustache statement, so feed line to it | ||
*/ | ||
if (this.mustacheStatement) { | ||
this.feedCharsToCurrentMustache('\n'); | ||
this.feedCharsToCurrentMustache(line); | ||
return; | ||
} | ||
if (this._isClosingTag(line)) { | ||
this._consumeNode(this._openedTags.pop()); | ||
/** | ||
* The line is an closing statement for a previously opened | ||
* block level tag | ||
*/ | ||
if (this.isClosingTag(line)) { | ||
this.consumeNode(this.openedTags.pop()); | ||
return; | ||
} | ||
if (!this._skipNewLine) { | ||
this._pushNewLine(); | ||
/** | ||
* Everything from here pushes a new line to the stack before | ||
* moving forward | ||
*/ | ||
if (!this.skipNewLine) { | ||
this.pushNewLine(); | ||
} | ||
const tag = Detector_1.getTag(line, this._line, 0, this._tagsDef); | ||
/** | ||
* Check if the current line is a tag or not. If yes, then handle | ||
* it appropriately | ||
*/ | ||
const tag = Detector_1.getTag(line, this.line, 0, this.tagsDef); | ||
if (tag) { | ||
this._handleTagOpening(line, tag); | ||
this._skipNewLine = true; | ||
this.handleTagOpening(line, tag); | ||
this.skipNewLine = true; | ||
return; | ||
} | ||
this._skipNewLine = false; | ||
const mustache = Detector_1.getMustache(line, this._line, 0); | ||
this.skipNewLine = false; | ||
/** | ||
* Check if the current line contains a mustache statement or not. If yes, | ||
* then handle it appropriately. | ||
*/ | ||
const mustache = Detector_1.getMustache(line, this.line, 0); | ||
if (mustache) { | ||
this._handleMustacheOpening(line, mustache); | ||
this.handleMustacheOpening(line, mustache); | ||
return; | ||
} | ||
this._consumeNode(this._getRawNode(line)); | ||
/** | ||
* Otherwise it is a raw line | ||
*/ | ||
this.consumeNode(this.getRawNode(line)); | ||
} | ||
_checkForErrors() { | ||
if (this._tagStatement) { | ||
const { tag } = this._tagStatement; | ||
throw Exceptions_1.unclosedParen({ line: tag.line, col: tag.col }, this._options.filename); | ||
/** | ||
* Checks for errors after the tokenizer completes it's work, so that we | ||
* can find broken statements or unclosed tags. | ||
*/ | ||
checkForErrors() { | ||
/** | ||
* We are done scanning the content and there is an open tagStatement | ||
* seeking for new content. Which means we are missing a closing | ||
* brace `)`. | ||
*/ | ||
if (this.tagStatement) { | ||
const { tag } = this.tagStatement; | ||
throw Exceptions_1.unclosedParen({ line: tag.line, col: tag.col }, this.options.filename); | ||
} | ||
if (this._mustacheStatement) { | ||
const { mustache } = this._mustacheStatement; | ||
throw Exceptions_1.unclosedCurlyBrace({ line: mustache.line, col: mustache.col }, this._options.filename); | ||
/** | ||
* We are done scanning the content and there is an open mustache statement | ||
* seeking for new content. Which means we are missing closing braces `}}`. | ||
*/ | ||
if (this.mustacheStatement) { | ||
const { mustache } = this.mustacheStatement; | ||
throw Exceptions_1.unclosedCurlyBrace({ line: mustache.line, col: mustache.col }, this.options.filename); | ||
} | ||
if (this._openedTags.length) { | ||
const openedTag = this._openedTags[this._openedTags.length - 1]; | ||
throw Exceptions_1.unclosedTag(openedTag.properties.name, openedTag.loc.start, this._options.filename); | ||
/** | ||
* A tag was opened, but forgot to close it | ||
*/ | ||
if (this.openedTags.length) { | ||
const openedTag = this.openedTags[this.openedTags.length - 1]; | ||
throw Exceptions_1.unclosedTag(openedTag.properties.name, openedTag.loc.start, this.options.filename); | ||
} | ||
} | ||
/** | ||
* Parse the template and generate an AST out of it | ||
*/ | ||
parse() { | ||
const lines = this._template.split(/\r\n|\r|\n/g); | ||
const lines = this.template.split(/\r\n|\r|\n/g); | ||
const linesLength = lines.length; | ||
while (this._line < linesLength) { | ||
const line = lines[this._line]; | ||
this._line++; | ||
this._processText(line); | ||
while (this.line < linesLength) { | ||
const line = lines[this.line]; | ||
this.line++; | ||
this.processText(line); | ||
} | ||
this._checkForErrors(); | ||
this.checkForErrors(); | ||
} | ||
} | ||
exports.Tokenizer = Tokenizer; |
{ | ||
"name": "edge-lexer", | ||
"version": "2.0.10", | ||
"version": "2.0.11", | ||
"description": "Parses raw markup files to converts them to Edge tokens", | ||
@@ -23,3 +23,3 @@ "main": "build/index.js", | ||
"prepublishOnly": "npm run build", | ||
"lint": "tslint --project tsconfig.json" | ||
"lint": "eslint . --ext=.ts" | ||
}, | ||
@@ -34,22 +34,22 @@ "keywords": [ | ||
"devDependencies": { | ||
"@adonisjs/mrm-preset": "^2.1.0", | ||
"@types/node": "^12.7.5", | ||
"@adonisjs/mrm-preset": "^2.2.4", | ||
"@types/node": "^13.7.1", | ||
"benchmark": "^2.1.4", | ||
"commitizen": "^4.0.3", | ||
"cz-conventional-changelog": "^3.0.2", | ||
"cz-conventional-changelog": "^3.1.0", | ||
"dedent": "^0.7.0", | ||
"del-cli": "^3.0.0", | ||
"doctoc": "^1.4.0", | ||
"husky": "^3.0.5", | ||
"eslint": "^6.8.0", | ||
"eslint-plugin-adonis": "^1.0.8", | ||
"husky": "^4.2.3", | ||
"japa": "^3.0.1", | ||
"japa-cli": "^1.0.1", | ||
"mrm": "^1.2.2", | ||
"np": "^5.1.0", | ||
"ts-node": "^8.3.0", | ||
"tslint": "^5.20.0", | ||
"tslint-eslint-rules": "^5.4.0", | ||
"typedoc": "^0.15.0", | ||
"typedoc-plugin-external-module-name": "^2.1.0", | ||
"typedoc-plugin-markdown": "^2.2.1", | ||
"typescript": "^3.6.3" | ||
"mrm": "^2.0.4", | ||
"np": "^6.0.0", | ||
"ts-node": "^8.6.2", | ||
"typedoc": "^0.16.9", | ||
"typedoc-plugin-external-module-name": "^3.0.0", | ||
"typedoc-plugin-markdown": "^2.2.16", | ||
"typescript": "^3.7.5" | ||
}, | ||
@@ -56,0 +56,0 @@ "dependencies": { |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
51014
1285
1