This commit is contained in:
Tutur33
2023-11-24 23:58:26 +01:00
parent 25395c0ee1
commit 938ad9d309
4191 changed files with 41 additions and 518781 deletions
-9
View File
@@ -1,9 +0,0 @@
# The MIT License
Copyright 2022 Harminder Virk, contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-428
View File
@@ -1,428 +0,0 @@
<div align="center"><img src="https://res.cloudinary.com/adonis-js/image/upload/v1620150474/edge-banner_tzmnox.jpg" width="600px"></div>
# Edge Parser
> Parser to convert edge templates to invokable functions
[![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url] [![synk-image]][synk-url]
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table of contents
- [Table of contents](#table-of-contents)
- [Usage](#usage)
- [Parser API](#parser-api)
- [generateAST(jsExpression, lexerLoc, filename)](#generateastjsexpression-lexerloc-filename)
- [transformAst(acornAst, filename)](#transformastacornast-filename)
- [tokenize (template, options: { filename })](#tokenize-template-options--filename-)
- [stringify(expression)](#stringifyexpression)
- [processToken(token, buffer)](#processtokentoken-buffer)
- [Supported Expressions](#supported-expressions)
- [Identifier](#identifier)
- [Literal](#literal)
- [ArrayExpression](#arrayexpression)
- [ObjectExpression](#objectexpression)
- [UnaryExpression](#unaryexpression)
- [BinaryExpression](#binaryexpression)
- [LogicalExpression](#logicalexpression)
- [MemberExpression](#memberexpression)
- [ConditionalExpression](#conditionalexpression)
- [CallExpression](#callexpression)
- [SequenceExpression](#sequenceexpression)
- [TemplateLiteral](#templateliteral)
- [ArrowFunctionExpression](#arrowfunctionexpression)
- [AwaitExpression](#awaitexpression)
- [FunctionDeclaration](#functiondeclaration)
- [BlockStatement](#blockstatement)
- [ChainExpression](#chainexpression)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
This repo is the **parser to convert edge templates** to a self invoked Javascript function.
## Usage
Install the package from npm registry as follows:
```sh
npm i edge-parser
# yarn
yarn add edge-parser
```
and then use it as follows
```js
import { Parser, EdgeBuffer, Stack } from 'edge-parser'
const filename = 'eval.edge'
const statePropertyName = 'state'
const escapeCallPath = 'escape'
const outputVar = 'out'
const rethrowCallPath = 'reThrow'
const parser = new Parser({}, new Stack(), {
statePropertyName,
escapeCallPath,
})
const buffer = new EdgeBuffer(filename, { outputVar, rethrowCallPath })
parser
.tokenize('Hello {{ username }}', { filename })
.forEach((token) => parser.processToken(token, buffer))
```
- All the first set of `const` declarations are the config values that impacts the compiled output.
- `filename` is required to ensure that exceptions stack traces point back to the correct filename.
- `statePropertyName` is the variable name from which the values should be accessed. For example: `{{ username }}` will be compiled as `state.username`. Leave it to empty, if state is not nested inside an object.
- `escapeCallPath` Reference to the `escape` method for escaping interpolation values. For example: `{{ username }}` will be compiled as `escape(state.username)`. The `escape` method should escape only strings and return the other data types as it is.
- `outputVar` is the variable name that holds the output of the compiled template.
- `rethrowCallPath` Reference to the `reThrow` method to raise the template exceptions with the current `$filename` and `$lineNumber`. Check the following compiled output to see how this function is called.
**Compiled output**
```js
let out = ''
let $lineNumber = 1
let $filename = 'eval.edge'
try {
out += 'Hello '
out += `${escape(state.username)}`
} catch (error) {
reThrow(error, $filename, $lineNumber)
}
return out
```
You can wrap the compiled output inside a function and invoke it as follows
```ts
/**
* Convert string to a function
*/
const fn = new Function('', `return function template (state, escape, reThrow) { ${output} }`)()
/**
* Template state
*/
const state = { username: 'virk' }
/**
* Escape function
*/
function escape(value: any) {
return value
}
/**
* Rethrow function
*/
function reThrow(error: Error) {
throw error
}
console.log(fn(state, escape, reThrow))
```
## Parser API
Along with parsing the main template, the parser also exposes the API, that tags can use to selectively parse the content of a tag.
#### generateAST(jsExpression, lexerLoc, filename)
Parses a string as a Javascript expression. The output is a valid [Estree expression](https://github.com/estree/estree)
The following example returns a [BinaryExpression](https://astexplorer.net/#/gist/0b6250a81804270a026fe39e3bc33fb6/latest)
```ts
const loc = {
start: { line: 1, col: 1 },
end: { line: 1, col: 1 },
}
const filename = 'eval.edge'
parser.utils.generateAST('2 + 2', loc, filename)
```
#### transformAst(acornAst, filename)
Transform the acorn AST and make it compatible with Edge runtime. This method mutates the inner nodes of the original AST.
```ts
const loc = {
start: { line: 1, col: 1 },
end: { line: 1, col: 1 },
}
const filename = 'eval.edge'
parser.utils.transformAst(parser.utils.generateAST('2 + 2', loc, filename), filename)
```
#### tokenize (template, options: { filename })
Returns an array of [lexer tokens](https://github.com/edge-js/lexer) for the given template. The method is a shortcut to self import the lexer module and then generating tokens.
```ts
const tokens = parser.tokenize('Hello {{ username }}', {
filename: 'eval.edge',
})
```
**Output**
```json
[
{
"type": "raw",
"line": 1,
"value": "Hello "
},
{
"type": "mustache",
"filename": "eval.edge",
"loc": {
"start": {
"line": 1,
"col": 8
},
"end": {
"line": 1,
"col": 20
}
},
"properties": {
"jsArg": " username "
}
}
]
```
#### stringify(expression)
Convert edge or acorn expression back to a string. This is helpful, when you mutate some nodes inside the expression and now want a valid Javascript string out of it.
```ts
const expression = parser.utils.generateAST(
'2 + 2',
{
start: { line: 1, col: 1 },
end: { line: 1, col: 1 },
},
'eval.edge'
)
expression.left.value = 3
parser.utils.stringify(expression) // returns 3 + 2
```
#### processToken(token, buffer)
You will often find yourself using this method as a tag author, when you want to recursively process all children of your tag
```ts
const byPass = {
block: true,
seekable: false,
name: 'bypass',
compile(parser, buffer, token) {
token.children.forEach((child) => parser.processToken(child, buffer))
},
}
```
and then use it as
```edge
@bypass
Hello {{ username }}
@endbypass
```
## Supported Expressions
The following expressions are supported by the parser. Can you also access the list of supported expressions as
```js
import { expressions } from 'edge-parser'
```
#### Identifier
The identifier are prefixed with `state.` In following statement `username` is the identifier
```
Hello {{ username }}
```
#### Literal
A string literal
```
Hello {{ 'Guest' }}
```
#### ArrayExpression
The `[1, 2, 3, 4]` is an array expression.
```
Evens are {{
[1, 2, 3, 4].filter((num) => num % 2 === 0)
}}
```
#### ObjectExpression
The `{ username: 'virk' }` is an Object expression
```
{{ toJSON({ username: 'virk' }) }}
```
#### UnaryExpression
Following are examples of `UnaryExpression`.
```
{{ typeof(username) }}
{{ !!username }}
```
#### BinaryExpression
Here `{{ 2 + 2 }}` is the binary expression
```
{{ 2 + 2 }} = 4
```
#### LogicalExpression
Following is the example of `LogicalExpression`.
```
{{ username || admin.username }}
```
#### MemberExpression
```
{{ username.toUpperCase() }}
```
#### ConditionalExpression
```
{{ username ? username : 'Guest' }}
```
#### CallExpression
```
{{ upper(username) }}
```
#### SequenceExpression
Sequence is not supported in mustache blocks and instead used inside tags. For example:
Everything inside `()` is a sequence expression.
```
@component('button', text = 'Submit', type = 'Primary')
```
#### TemplateLiteral
```
{{ Hello `${username}` }}
```
#### ArrowFunctionExpression
```
{{
users.map((user) => {
return user.username
})
}}
```
#### AwaitExpression
```
{{ await foo() }}
```
#### FunctionDeclaration
```
{{ function foo () {} }}
```
#### BlockStatement
Here the `map` callback is the block statement
```
{{
users.map(() => {})
}}
```
#### ChainExpression
Support for optional chaining
```
{{ user?.username }}
```
#### NewExpression
```js
{{ new User() }}
```
#### ReturnStatement
In the following example `return` keyword is a return statement
```js
users.map((user) => {
return user.username
})
```
#### ThisExpression
Support for the this keyword
```js
{{ this.state }}
```
#### SpreadElement
Support for the spread element
```js
{{ [...users] }}
```
[gh-workflow-image]: https://img.shields.io/github/workflow/status/edge-js/parser/test?style=for-the-badge
[gh-workflow-url]: https://github.com/edge-js/parser/actions/workflows/test.yml "Github action"
[npm-image]: https://img.shields.io/npm/v/edge-parser.svg?style=for-the-badge&logo=npm
[npm-url]: https://npmjs.org/package/edge-parser 'npm'
[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
[license-url]: LICENSE.md
[license-image]: https://img.shields.io/github/license/edge-js/lexer?style=for-the-badge
[synk-image]: https://img.shields.io/snyk/vulnerabilities/github/edge-js/parser?label=Synk%20Vulnerabilities&style=for-the-badge
[synk-url]: https://snyk.io/test/github/edge-js/parser?targetFile=package.json 'synk'
-141
View File
@@ -1,141 +0,0 @@
{
"name": "edge-parser",
"version": "8.2.2",
"description": "Parser for edge template engine",
"main": "build/index.js",
"files": [
"build/src",
"build/index.d.ts",
"build/index.js"
],
"scripts": {
"pretest": "npm run lint",
"test": "node .bin/test.js",
"build": "npm run compile",
"mrm": "mrm --preset=@adonisjs/mrm-preset",
"commit": "git-cz",
"test:win": "node ./node_modules/japa-cli/index.js",
"clean": "del build",
"compile": "npm run lint && npm run clean && tsc",
"release": "np --message=\"chore(release): %s\"",
"version": "npm run build",
"lint": "eslint . --ext=.ts",
"prepublishOnly": "npm run build",
"sync-labels": "github-label-sync --labels ./node_modules/@adonisjs/mrm-preset/gh-labels.json edge-js/parser",
"format": "prettier --write ."
},
"keywords": [
"edge",
"template",
"template-engine"
],
"author": "virk",
"license": "MIT",
"dependencies": {
"acorn": "^8.8.2",
"astring": "^1.8.4",
"edge-error": "^3.0.0",
"edge-lexer": "^5.0.2",
"js-stringify": "^1.0.2"
},
"devDependencies": {
"@adonisjs/mrm-preset": "^5.0.3",
"@adonisjs/require-ts": "^2.0.13",
"@types/acorn": "^6.0.0",
"@types/astring": "^1.7.0",
"@types/node": "^18.15.3",
"commitizen": "^4.3.0",
"cz-conventional-changelog": "^3.3.0",
"dedent-js": "^1.0.1",
"del-cli": "^5.0.0",
"doctoc": "^2.2.1",
"eslint": "^8.36.0",
"eslint-config-prettier": "^8.7.0",
"eslint-plugin-adonis": "^2.1.0",
"eslint-plugin-prettier": "^4.2.1",
"github-label-sync": "^2.3.1",
"husky": "^8.0.3",
"japa": "^4.0.0",
"mrm": "^4.1.14",
"np": "^7.6.4",
"prettier": "^2.8.5",
"typescript": "^5.0.2",
"youch": "^3.2.3"
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"nyc": {
"exclude": [
"test"
],
"extension": [
".ts"
]
},
"gitHooks": {
"commit-msg": "node ./node_modules/@adonisjs/mrm-preset/validateCommit/conventional/validate.js"
},
"husky": {
"hooks": {
"commit-msg": "node ./node_modules/@adonisjs/mrm-preset/validateCommit/conventional/validate.js"
}
},
"np": {
"contents": ".",
"anyBranch": false
},
"repository": {
"type": "git",
"url": "git+https://github.com/edge-js/parser.git"
},
"bugs": {
"url": "https://github.com/edge-js/parser/issues"
},
"homepage": "https://github.com/edge-js/parser#readme",
"mrmConfig": {
"core": false,
"license": "MIT",
"services": [
"github-actions"
],
"minNodeVersion": "14.15.4",
"probotApps": [
"stale",
"lock"
],
"runGhActionsOnWindows": true
},
"eslintConfig": {
"extends": [
"plugin:adonis/typescriptPackage",
"prettier"
],
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": [
"error",
{
"endOfLine": "auto"
}
]
}
},
"eslintIgnore": [
"build"
],
"prettier": {
"trailingComma": "es5",
"semi": false,
"singleQuote": true,
"useTabs": false,
"quoteProps": "consistent",
"bracketSpacing": true,
"arrowParens": "always",
"printWidth": 100
}
}