This commit is contained in:
Tutur33
2023-11-24 22:35:41 +01:00
parent 3c0b507a93
commit 7644b2a0f7
45165 changed files with 4803356 additions and 3 deletions
+30
View File
@@ -0,0 +1,30 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x, 14.x, 15.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build --if-present
- run: npm test
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright Ayon Lee and other Node 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.
+62
View File
@@ -0,0 +1,62 @@
# Node's processTopLevelAwait
Standalone util function from Node.js core to process await statements in REPL.
Since v10.0.0, Node.js introduced a new experimental feature to support `await`
keyword in the REPL by using the argument `--experimental-repl-await`, however,
if a user wants to implement a custom REPL console, there would be no
await-support at all, to achieve such a goal, this package clones the internal
module of await-support to form a standalone version, allowing users share the
benefits of await-support in their own REPL environments.
See
[Node.js docs](https://nodejs.org/dist/latest-v11.x/docs/api/repl.html#repl_await_keyword)
for more details, and contribute to the
[original source](https://github.com/nodejs/node/blob/master/lib/internal/repl/await.js).
## Example
```javascript
const repl = require("repl");
const vm = require("vm");
const { processTopLevelAwait } = require("node-repl-await");
function isRecoverableError(error) {
if (error.name === 'SyntaxError') {
return /^(Unexpected end of input|Unexpected token)/.test(error.message);
}
return false;
}
async function myEval(code, context, filename, callback) {
code = processTopLevelAwait(code) || code;
try {
let result = await vm.runInNewContext(code, context);
callback(null, result);
} catch (e) {
if (isRecoverableError(e)) {
callback(new repl.Recoverable(e));
} else {
console.log(e);
}
}
}
repl.start({ prompt: '> ', eval: myEval });
```
## API
```typescript
function processTopLevelAwait(src: string): string | void
```
Tries to wrap the given source code to an immediately-invoked function if it
contains any top level `await` statement in the form of
```js
(async () => { /* source code */ })()
```
If no `await` presents or processing failed, the function returns `null`.
+1
View File
@@ -0,0 +1 @@
export function processTopLevelAwait(src: string): string | void;
+143
View File
@@ -0,0 +1,143 @@
'use strict';
const acorn = require('acorn');
const walk = require('acorn-walk');
const privateMethods = require('acorn-private-methods');
const classFields = require('acorn-class-fields');
const staticClassFeatures = require('acorn-static-class-features');
const parser = acorn.Parser.extend(
privateMethods,
classFields,
staticClassFeatures
);
const noop = () => { };
const visitorsWithoutAncestors = {
ClassDeclaration(node, state, c) {
if (state.ancestors[state.ancestors.length - 2] === state.body) {
state.prepend(node, `${node.id.name}=`);
}
walk.base.ClassDeclaration(node, state, c);
},
ForOfStatement(node, state, c) {
if (node.await === true) {
state.containsAwait = true;
}
walk.base.ForOfStatement(node, state, c);
},
FunctionDeclaration(node, state, c) {
state.prepend(node, `${node.id.name}=`);
},
FunctionExpression: noop,
ArrowFunctionExpression: noop,
MethodDefinition: noop,
AwaitExpression(node, state, c) {
state.containsAwait = true;
walk.base.AwaitExpression(node, state, c);
},
ReturnStatement(node, state, c) {
state.containsReturn = true;
walk.base.ReturnStatement(node, state, c);
},
VariableDeclaration(node, state, c) {
if (node.kind === 'var' ||
state.ancestors[state.ancestors.length - 2] === state.body) {
if (node.declarations.length === 1) {
state.replace(node.start, node.start + node.kind.length, 'void');
} else {
state.replace(node.start, node.start + node.kind.length, 'void (');
}
for (const decl of node.declarations) {
state.prepend(decl, '(');
state.append(decl, decl.init ? ')' : '=undefined)');
}
if (node.declarations.length !== 1) {
state.append(node.declarations[node.declarations.length - 1], ')');
}
}
walk.base.VariableDeclaration(node, state, c);
}
};
const visitors = {};
for (const nodeType of Object.keys(walk.base)) {
const callback = visitorsWithoutAncestors[nodeType] || walk.base[nodeType];
visitors[nodeType] = (node, state, c) => {
const isNew = node !== state.ancestors[state.ancestors.length - 1];
if (isNew) {
state.ancestors.push(node);
}
callback(node, state, c);
if (isNew) {
state.ancestors.pop();
}
};
}
function processTopLevelAwait(src) {
const wrapped = `(async () => { ${src} })()`;
const wrappedArray = wrapped.split('');
let root;
try {
root = parser.parse(wrapped, { ecmaVersion: 11 });
} catch (e) {
return null;
}
const body = root.body[0].expression.callee.body;
const state = {
body,
ancestors: [],
replace(from, to, str) {
for (var i = from; i < to; i++) {
wrappedArray[i] = '';
}
if (from === to) str += wrappedArray[from];
wrappedArray[from] = str;
},
prepend(node, str) {
wrappedArray[node.start] = str + wrappedArray[node.start];
},
append(node, str) {
wrappedArray[node.end - 1] += str;
},
containsAwait: false,
containsReturn: false
};
walk.recursive(body, state, visitors);
// Do not transform if
// 1. False alarm: there isn't actually an await expression.
// 2. There is a top-level return, which is not allowed.
if (!state.containsAwait || state.containsReturn) {
return null;
}
const last = body.body[body.body.length - 1];
if (last.type === 'ExpressionStatement') {
// For an expression statement of the form
// ( expr ) ;
// ^^^^^^^^^^ // last
// ^^^^ // last.expression
//
// We do not want the left parenthesis before the `return` keyword;
// therefore we prepend the `return (` to `last`.
//
// On the other hand, we do not want the right parenthesis after the
// semicolon. Since there can only be more right parentheses between
// last.expression.end and the semicolon, appending one more to
// last.expression should be fine.
state.prepend(last, 'return (');
state.append(last.expression, ')');
}
return wrappedArray.join('');
}
module.exports = {
processTopLevelAwait
};
+38
View File
@@ -0,0 +1,38 @@
{
"name": "node-repl-await",
"version": "0.1.2",
"description": "Standalone util function from Node.js core to process await statements in REPL.",
"main": "index.js",
"types": "index.d.ts",
"scripts": {
"test": "mocha"
},
"repository": {
"type": "git",
"url": "git+https://github.com/hyurl/node-repl-await.git"
},
"keywords": [
"repl",
"await",
"async",
"shell",
"console",
"terminal"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/nodejs/node/issues"
},
"homepage": "https://nodejs.org",
"dependencies": {
"acorn": "^8.0.5",
"acorn-class-fields": "^1.0.0",
"acorn-private-methods": "^1.0.0",
"acorn-static-class-features": "^1.0.0",
"acorn-walk": "^8.0.2"
},
"devDependencies": {
"@types/node": "^11.15.2",
"mocha": "^6.2.2"
}
}
+21
View File
@@ -0,0 +1,21 @@
/* global describe, it */
const assert = require("assert");
const { processTopLevelAwait } = require(".");
describe("processTopLevelAwait", () => {
it("should process statements with leading await keywords", () => {
assert.strictEqual(processTopLevelAwait("await 123"), "(async () => { return (await 123) })()");
assert.strictEqual(processTopLevelAwait("await 123;"), "(async () => { return (await 123); })()");
});
it("should process statements with await keywords in the middle", () => {
assert.strictEqual(processTopLevelAwait("foo = await 123"), "(async () => { return (foo = await 123) })()");
assert.strictEqual(processTopLevelAwait("foo = await 123;"), "(async () => { return (foo = await 123); })()");
assert.strictEqual(processTopLevelAwait("let foo; foo = await 123;"), "(async () => { void (foo=undefined); return (foo = await 123); })()");
});
it("should not process any statement without top level await keyword", () => {
assert.strictEqual(processTopLevelAwait("let foo = 123"), null);
assert.strictEqual(processTopLevelAwait("async function () { await 123; }"), null);
});
});