Upgrade test infrastructure
- Enhance (de)serialization logic and slim down generated JSON - Add more commands to `bolt-test` and improve support for aliases
This commit is contained in:
parent
608728ade9
commit
a86324a3c9
9 changed files with 306 additions and 116 deletions
10
package-lock.json
generated
10
package-lock.json
generated
|
@ -419,6 +419,11 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/uuid": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw=="
|
||||||
|
},
|
||||||
"@types/yargs": {
|
"@types/yargs": {
|
||||||
"version": "15.0.5",
|
"version": "15.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz",
|
||||||
|
@ -4976,6 +4981,11 @@
|
||||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"uuid": {
|
||||||
|
"version": "8.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz",
|
||||||
|
"integrity": "sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg=="
|
||||||
|
},
|
||||||
"v8-compile-cache": {
|
"v8-compile-cache": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz",
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"watch": "webpack --watch --config webpack.dev.js",
|
"watch": "webpack --watch --config webpack.dev.js",
|
||||||
"prepare": "webpack --config webpack.dev.js",
|
"prepare": "webpack --config webpack.dev.js",
|
||||||
"test": "node build/bin/bolt-test.js",
|
"test": "node build/bin/bolt-test.js",
|
||||||
"update-lkg": "node build/bin/bolt-test.js snapshot lkg"
|
"update-lkg": "node build/bin/bolt-test.js create-snapshot lkg"
|
||||||
},
|
},
|
||||||
"author": "Sam Vervaeck <vervaeck.sam@skynet.be>",
|
"author": "Sam Vervaeck <vervaeck.sam@skynet.be>",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
|
@ -24,6 +24,7 @@
|
||||||
"@types/node": "^14.0.11",
|
"@types/node": "^14.0.11",
|
||||||
"@types/ora": "^3.2.0",
|
"@types/ora": "^3.2.0",
|
||||||
"@types/semver": "^7.2.0",
|
"@types/semver": "^7.2.0",
|
||||||
|
"@types/uuid": "^8.0.0",
|
||||||
"@types/yargs": "^15.0.5",
|
"@types/yargs": "^15.0.5",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"fs-extra": "^9.0.1",
|
"fs-extra": "^9.0.1",
|
||||||
|
@ -37,6 +38,7 @@
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.2",
|
||||||
"source-map-support": "^0.5.19",
|
"source-map-support": "^0.5.19",
|
||||||
|
"uuid": "^8.1.0",
|
||||||
"yargs": "^15.3.1"
|
"yargs": "^15.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { TextSpan } from "./text"
|
||||||
import { Value } from "./evaluator"
|
import { Value } from "./evaluator"
|
||||||
import { Package } from "./package"
|
import { Package } from "./package"
|
||||||
import { Diagnostic } from "./diagnostics";
|
import { Diagnostic } from "./diagnostics";
|
||||||
import { serializeTag, serialize, JsonObject } from "./util";
|
import { serializeTag, serialize } from "./util";
|
||||||
|
|
||||||
let nextNodeId = 1;
|
let nextNodeId = 1;
|
||||||
|
|
||||||
|
@ -31,13 +31,14 @@ export abstract class Syntax {
|
||||||
}
|
}
|
||||||
|
|
||||||
[serializeTag]() {
|
[serializeTag]() {
|
||||||
const result: JsonObject = {};
|
const result: any[] = [];
|
||||||
for (const key of Object.keys(this)) {
|
for (const key of Object.keys(this)) {
|
||||||
if (key === 'parentNode' || key === 'errors' || key === 'type' || key === 'id') {
|
if (key === 'parentNode' || key === 'errors' || key === 'type' || key === 'id') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
result[key] = serialize((this as any)[key]);
|
result.push((this as any)[key]);
|
||||||
}
|
}
|
||||||
|
result.push(this.span);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { Package } from "./package";
|
||||||
|
|
||||||
import { Diagnostic } from "./diagnostics";
|
import { Diagnostic } from "./diagnostics";
|
||||||
|
|
||||||
import { serializeTag, serialize, JsonObject } from "./util";
|
import { serializeTag, serialize } from "./util";
|
||||||
|
|
||||||
let nextNodeId = 1;
|
let nextNodeId = 1;
|
||||||
|
|
||||||
|
@ -27,13 +27,14 @@ export abstract class SyntaxBase {
|
||||||
this.id = nextNodeId++;
|
this.id = nextNodeId++;
|
||||||
}
|
}
|
||||||
[serializeTag]() {
|
[serializeTag]() {
|
||||||
const result: JsonObject = {};
|
const result: any[] = [];
|
||||||
for (const key of Object.keys(this)) {
|
for (const key of Object.keys(this)) {
|
||||||
if (key === 'parentNode' || key === 'errors' || key === 'type' || key === 'id') {
|
if (key === 'parentNode' || key === 'errors' || key === 'type' || key === 'id') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
result[key] = serialize((this as any)[key]);
|
result.push((this as any)[key]);
|
||||||
}
|
}
|
||||||
|
result.push(this.span);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
*preorder() {
|
*preorder() {
|
||||||
|
@ -3253,6 +3254,8 @@ export function kindToString(kind: SyntaxKind): string { if (SyntaxKind[kind] ==
|
||||||
|
|
||||||
export type Syntax = EndOfFile | BoltStringLiteral | BoltIntegerLiteral | BoltIdentifier | BoltOperator | BoltAssignment | BoltComma | BoltSemi | BoltColon | BoltColonColon | BoltDot | BoltDotDot | BoltRArrow | BoltRArrowAlt | BoltLArrow | BoltEqSign | BoltGtSign | BoltExMark | BoltLtSign | BoltVBar | BoltWhereKeyword | BoltQuoteKeyword | BoltFnKeyword | BoltForeignKeyword | BoltForKeyword | BoltLetKeyword | BoltReturnKeyword | BoltLoopKeyword | BoltYieldKeyword | BoltMatchKeyword | BoltImportKeyword | BoltExportKeyword | BoltPubKeyword | BoltModKeyword | BoltMutKeyword | BoltEnumKeyword | BoltStructKeyword | BoltTypeKeyword | BoltTraitKeyword | BoltImplKeyword | BoltParenthesized | BoltBraced | BoltBracketed | BoltSourceFile | BoltQualName | BoltTypeOfExpression | BoltReferenceTypeExpression | BoltFunctionTypeExpression | BoltLiftedTypeExpression | BoltTypeParameter | BoltBindPattern | BoltTypePattern | BoltExpressionPattern | BoltTuplePatternElement | BoltTuplePattern | BoltRecordFieldPattern | BoltRecordPattern | BoltQuoteExpression | BoltTupleExpression | BoltReferenceExpression | BoltMemberExpression | BoltFunctionExpression | BoltCallExpression | BoltYieldExpression | BoltMatchArm | BoltMatchExpression | BoltCase | BoltCaseExpression | BoltBlockExpression | BoltConstantExpression | BoltReturnStatement | BoltConditionalCase | BoltConditionalStatement | BoltResumeStatement | BoltExpressionStatement | BoltLoopStatement | BoltParameter | BoltModule | BoltFunctionDeclaration | BoltVariableDeclaration | BoltPlainImportSymbol | BoltImportDirective | BoltPlainExportSymbol | BoltExportDirective | BoltTraitDeclaration | BoltImplDeclaration | BoltTypeAliasDeclaration | BoltRecordField | BoltRecordDeclaration | BoltMacroCall | JSIdentifier | JSString | JSInteger | JSFromKeyword | JSReturnKeyword | JSTryKeyword | JSFinallyKeyword | JSCatchKeyword | JSImportKeyword | JSAsKeyword | JSConstKeyword | JSLetKeyword | JSExportKeyword | JSFunctionKeyword | JSWhileKeyword | JSForKeyword | JSOperator | JSCloseBrace | JSCloseBracket | JSCloseParen | JSOpenBrace | JSOpenBracket | JSOpenParen | JSSemi | JSComma | JSDot | JSDotDotDot | JSMulOp | JSAddOp | JSDivOp | JSSubOp | JSLtOp | JSGtOp | JSBOrOp | JSBXorOp | JSBAndOp | JSBNotOp | JSNotOp | JSBindPattern | JSConstantExpression | JSMemberExpression | JSCallExpression | JSBinaryExpression | JSUnaryExpression | JSNewExpression | JSSequenceExpression | JSConditionalExpression | JSLiteralExpression | JSReferenceExpression | JSCatchBlock | JSTryCatchStatement | JSExpressionStatement | JSConditionalCase | JSConditionalStatement | JSReturnStatement | JSParameter | JSImportStarBinding | JSImportAsBinding | JSImportDeclaration | JSFunctionDeclaration | JSArrowFunctionDeclaration | JSLetDeclaration | JSSourceFile;
|
export type Syntax = EndOfFile | BoltStringLiteral | BoltIntegerLiteral | BoltIdentifier | BoltOperator | BoltAssignment | BoltComma | BoltSemi | BoltColon | BoltColonColon | BoltDot | BoltDotDot | BoltRArrow | BoltRArrowAlt | BoltLArrow | BoltEqSign | BoltGtSign | BoltExMark | BoltLtSign | BoltVBar | BoltWhereKeyword | BoltQuoteKeyword | BoltFnKeyword | BoltForeignKeyword | BoltForKeyword | BoltLetKeyword | BoltReturnKeyword | BoltLoopKeyword | BoltYieldKeyword | BoltMatchKeyword | BoltImportKeyword | BoltExportKeyword | BoltPubKeyword | BoltModKeyword | BoltMutKeyword | BoltEnumKeyword | BoltStructKeyword | BoltTypeKeyword | BoltTraitKeyword | BoltImplKeyword | BoltParenthesized | BoltBraced | BoltBracketed | BoltSourceFile | BoltQualName | BoltTypeOfExpression | BoltReferenceTypeExpression | BoltFunctionTypeExpression | BoltLiftedTypeExpression | BoltTypeParameter | BoltBindPattern | BoltTypePattern | BoltExpressionPattern | BoltTuplePatternElement | BoltTuplePattern | BoltRecordFieldPattern | BoltRecordPattern | BoltQuoteExpression | BoltTupleExpression | BoltReferenceExpression | BoltMemberExpression | BoltFunctionExpression | BoltCallExpression | BoltYieldExpression | BoltMatchArm | BoltMatchExpression | BoltCase | BoltCaseExpression | BoltBlockExpression | BoltConstantExpression | BoltReturnStatement | BoltConditionalCase | BoltConditionalStatement | BoltResumeStatement | BoltExpressionStatement | BoltLoopStatement | BoltParameter | BoltModule | BoltFunctionDeclaration | BoltVariableDeclaration | BoltPlainImportSymbol | BoltImportDirective | BoltPlainExportSymbol | BoltExportDirective | BoltTraitDeclaration | BoltImplDeclaration | BoltTypeAliasDeclaration | BoltRecordField | BoltRecordDeclaration | BoltMacroCall | JSIdentifier | JSString | JSInteger | JSFromKeyword | JSReturnKeyword | JSTryKeyword | JSFinallyKeyword | JSCatchKeyword | JSImportKeyword | JSAsKeyword | JSConstKeyword | JSLetKeyword | JSExportKeyword | JSFunctionKeyword | JSWhileKeyword | JSForKeyword | JSOperator | JSCloseBrace | JSCloseBracket | JSCloseParen | JSOpenBrace | JSOpenBracket | JSOpenParen | JSSemi | JSComma | JSDot | JSDotDotDot | JSMulOp | JSAddOp | JSDivOp | JSSubOp | JSLtOp | JSGtOp | JSBOrOp | JSBXorOp | JSBAndOp | JSBNotOp | JSNotOp | JSBindPattern | JSConstantExpression | JSMemberExpression | JSCallExpression | JSBinaryExpression | JSUnaryExpression | JSNewExpression | JSSequenceExpression | JSConditionalExpression | JSLiteralExpression | JSReferenceExpression | JSCatchBlock | JSTryCatchStatement | JSExpressionStatement | JSConditionalCase | JSConditionalStatement | JSReturnStatement | JSParameter | JSImportStarBinding | JSImportAsBinding | JSImportDeclaration | JSFunctionDeclaration | JSArrowFunctionDeclaration | JSLetDeclaration | JSSourceFile;
|
||||||
|
|
||||||
|
export const NODE_TYPES = { EndOfFile, BoltStringLiteral, BoltIntegerLiteral, BoltIdentifier, BoltOperator, BoltAssignment, BoltComma, BoltSemi, BoltColon, BoltColonColon, BoltDot, BoltDotDot, BoltRArrow, BoltRArrowAlt, BoltLArrow, BoltEqSign, BoltGtSign, BoltExMark, BoltLtSign, BoltVBar, BoltWhereKeyword, BoltQuoteKeyword, BoltFnKeyword, BoltForeignKeyword, BoltForKeyword, BoltLetKeyword, BoltReturnKeyword, BoltLoopKeyword, BoltYieldKeyword, BoltMatchKeyword, BoltImportKeyword, BoltExportKeyword, BoltPubKeyword, BoltModKeyword, BoltMutKeyword, BoltEnumKeyword, BoltStructKeyword, BoltTypeKeyword, BoltTraitKeyword, BoltImplKeyword, BoltParenthesized, BoltBraced, BoltBracketed, BoltSourceFile, BoltQualName, BoltTypeOfExpression, BoltReferenceTypeExpression, BoltFunctionTypeExpression, BoltLiftedTypeExpression, BoltTypeParameter, BoltBindPattern, BoltTypePattern, BoltExpressionPattern, BoltTuplePatternElement, BoltTuplePattern, BoltRecordFieldPattern, BoltRecordPattern, BoltQuoteExpression, BoltTupleExpression, BoltReferenceExpression, BoltMemberExpression, BoltFunctionExpression, BoltCallExpression, BoltYieldExpression, BoltMatchArm, BoltMatchExpression, BoltCase, BoltCaseExpression, BoltBlockExpression, BoltConstantExpression, BoltReturnStatement, BoltConditionalCase, BoltConditionalStatement, BoltResumeStatement, BoltExpressionStatement, BoltLoopStatement, BoltParameter, BoltModule, BoltFunctionDeclaration, BoltVariableDeclaration, BoltPlainImportSymbol, BoltImportDirective, BoltPlainExportSymbol, BoltExportDirective, BoltTraitDeclaration, BoltImplDeclaration, BoltTypeAliasDeclaration, BoltRecordField, BoltRecordDeclaration, BoltMacroCall, JSIdentifier, JSString, JSInteger, JSFromKeyword, JSReturnKeyword, JSTryKeyword, JSFinallyKeyword, JSCatchKeyword, JSImportKeyword, JSAsKeyword, JSConstKeyword, JSLetKeyword, JSExportKeyword, JSFunctionKeyword, JSWhileKeyword, JSForKeyword, JSOperator, JSCloseBrace, JSCloseBracket, JSCloseParen, JSOpenBrace, JSOpenBracket, JSOpenParen, JSSemi, JSComma, JSDot, JSDotDotDot, JSMulOp, JSAddOp, JSDivOp, JSSubOp, JSLtOp, JSGtOp, JSBOrOp, JSBXorOp, JSBAndOp, JSBNotOp, JSNotOp, JSBindPattern, JSConstantExpression, JSMemberExpression, JSCallExpression, JSBinaryExpression, JSUnaryExpression, JSNewExpression, JSSequenceExpression, JSConditionalExpression, JSLiteralExpression, JSReferenceExpression, JSCatchBlock, JSTryCatchStatement, JSExpressionStatement, JSConditionalCase, JSConditionalStatement, JSReturnStatement, JSParameter, JSImportStarBinding, JSImportAsBinding, JSImportDeclaration, JSFunctionDeclaration, JSArrowFunctionDeclaration, JSLetDeclaration, JSSourceFile };
|
||||||
|
|
||||||
export enum SyntaxKind {
|
export enum SyntaxKind {
|
||||||
EndOfFile,
|
EndOfFile,
|
||||||
BoltStringLiteral,
|
BoltStringLiteral,
|
||||||
|
|
|
@ -1,27 +1,33 @@
|
||||||
|
|
||||||
import "source-map-support/register"
|
import "source-map-support/register"
|
||||||
|
import "reflect-metadata"
|
||||||
|
|
||||||
import * as fs from "fs-extra"
|
import * as fs from "fs-extra"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import * as crypto from "crypto"
|
import * as crypto from "crypto"
|
||||||
|
|
||||||
|
import { v4 as uuidv4 } from "uuid"
|
||||||
import yargs from "yargs"
|
import yargs from "yargs"
|
||||||
import yaml from "js-yaml"
|
import yaml, { FAILSAFE_SCHEMA } from "js-yaml"
|
||||||
import { sync as globSync } from "glob"
|
import { sync as globSync } from "glob"
|
||||||
import ora from "ora"
|
import ora from "ora"
|
||||||
|
|
||||||
import { Parser } from "../parser"
|
import { Parser } from "../parser"
|
||||||
import { Scanner } from "../scanner"
|
import { Scanner } from "../scanner"
|
||||||
import { SyntaxKind, Syntax } from "../ast"
|
import { SyntaxKind, Syntax } from "../ast"
|
||||||
import { Json, serialize, JsonObject, MapLike, upsearchSync, deepEqual } from "../util"
|
import { Json, serialize, JsonObject, MapLike, upsearchSync, deepEqual, serializeTag, deserializable, deserialize } from "../util"
|
||||||
import { DiagnosticIndex } from "../diagnostics"
|
import { DiagnosticIndex, DiagnosticPrinter, E_TESTS_DO_NOT_COMPARE, E_INVALID_TEST_COMPARE } from "../diagnostics"
|
||||||
import { TextFile, TextPos, TextSpan } from "../text"
|
import { TextFile, TextPos, TextSpan } from "../text"
|
||||||
|
|
||||||
const PACKAGE_ROOT = path.dirname(upsearchSync('package.json')!);
|
const PACKAGE_ROOT = path.dirname(upsearchSync('package.json')!);
|
||||||
const STORAGE_DIR = path.join(PACKAGE_ROOT, '.test-storage');
|
const STORAGE_DIR = path.join(PACKAGE_ROOT, '.test-storage');
|
||||||
|
|
||||||
|
const diagnostics = new DiagnosticPrinter();
|
||||||
const spinner = ora(`Initializing test session ...`).start();
|
const spinner = ora(`Initializing test session ...`).start();
|
||||||
|
|
||||||
|
// TODO move some logic from TestSession to TestSuite
|
||||||
|
// TODO hash the entire code base and have it serve as a unique key for TestSession
|
||||||
|
|
||||||
function toArray<T>(value: T | T[]): T[] {
|
function toArray<T>(value: T | T[]): T[] {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value;
|
return value;
|
||||||
|
@ -29,6 +35,7 @@ function toArray<T>(value: T | T[]): T[] {
|
||||||
return value === undefined || value === null ? [] : [ value ]
|
return value === undefined || value === null ? [] : [ value ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@deserializable()
|
||||||
class Test {
|
class Test {
|
||||||
|
|
||||||
public key: string;
|
public key: string;
|
||||||
|
@ -45,6 +52,15 @@ class Test {
|
||||||
this.key = hash([text, data]);
|
this.key = hash([text, data]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[serializeTag]() {
|
||||||
|
return [
|
||||||
|
this.span,
|
||||||
|
this.type,
|
||||||
|
this.text,
|
||||||
|
this.data,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LoadTestsOptions {
|
interface LoadTestsOptions {
|
||||||
|
@ -52,6 +68,14 @@ interface LoadTestsOptions {
|
||||||
exclude: string[];
|
exclude: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TestSuite {
|
||||||
|
|
||||||
|
constructor(private tests: Test[]) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
class TestSession {
|
class TestSession {
|
||||||
|
|
||||||
private failCount = 0;
|
private failCount = 0;
|
||||||
|
@ -59,7 +83,7 @@ class TestSession {
|
||||||
public key: string;
|
public key: string;
|
||||||
|
|
||||||
constructor(private tests: Test[] = []) {
|
constructor(private tests: Test[] = []) {
|
||||||
this.key = `${Date.now().toString()}-${Math.random()}`;
|
this.key = uuidv4();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAllTests() {
|
public getAllTests() {
|
||||||
|
@ -108,9 +132,13 @@ class TestSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
public save() {
|
public save() {
|
||||||
|
fs.mkdirpSync(path.join(STORAGE_DIR, 'tests'));
|
||||||
|
for (const test of this.tests) {
|
||||||
|
fs.writeFileSync(path.join(STORAGE_DIR, 'tests', test.key), JSON.stringify(serialize(test)), 'utf8');
|
||||||
|
}
|
||||||
fs.mkdirpSync(path.join(STORAGE_DIR, 'snapshots', this.key))
|
fs.mkdirpSync(path.join(STORAGE_DIR, 'snapshots', this.key))
|
||||||
for (const test of this.tests) {
|
for (const test of this.tests) {
|
||||||
fs.writeFileSync(path.join(STORAGE_DIR, 'snapshots', this.key, test.key), JSON.stringify(test.result), 'utf8');
|
fs.writeFileSync(path.join(STORAGE_DIR, 'snapshots', this.key, test.key), JSON.stringify(serialize(test.result)), 'utf8');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,14 +148,24 @@ class TestSession {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function compare(actual: string, expected: string) {
|
function compare(actualKey: string, expectedKey: string) {
|
||||||
for (const testKey of fs.readdirSync(path.join(STORAGE_DIR, 'snapshots', actual))) {
|
|
||||||
const actualTestResult = readJson(path.join(STORAGE_DIR, 'snapshots', actual, testKey))!;
|
for (const testKey of fs.readdirSync(path.join(STORAGE_DIR, 'snapshots', actualKey))) {
|
||||||
const expectedTestResult = readJson(path.join(STORAGE_DIR, 'snapshots', expected, testKey));
|
|
||||||
if (!deepEqual(actualTestResult, expectedTestResult)) {
|
const test = deserialize(readJson(path.join(STORAGE_DIR, 'tests', testKey)))
|
||||||
spinner.warn(`Test ${testKey} does not compare to its expected value.`)
|
|
||||||
|
const actualTestData = deserialize(readJson(path.join(STORAGE_DIR, 'snapshots', actualKey, testKey))!);
|
||||||
|
const expectedTestData = deserialize(readJson(path.join(STORAGE_DIR, 'snapshots', expectedKey, testKey)));
|
||||||
|
if (!deepEqual(actualTestData.result, expectedTestData.result)) {
|
||||||
|
diagnostics.add({
|
||||||
|
message: E_TESTS_DO_NOT_COMPARE,
|
||||||
|
severity: 'error',
|
||||||
|
node: test,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isWhiteSpace(ch: string): boolean {
|
function isWhiteSpace(ch: string): boolean {
|
||||||
|
@ -250,9 +288,34 @@ function* loadTests(filepath: string): IterableIterator<Test> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findSnapshot(name: string): string | null {
|
||||||
|
|
||||||
|
// If `name` directly refers to a snapshot, we don't have any more work to do.
|
||||||
|
if (fs.existsSync(path.join(STORAGE_DIR, 'snapshots', name))) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to read an alias, returning early if it was indeed found
|
||||||
|
const ref = tryReadFileSync(path.join(STORAGE_DIR, 'aliases', name));
|
||||||
|
if (ref !== null) {
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't support any more refs at the moment, so we indicate failure
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function readJson(filename: string): Json | null {
|
function readJson(filename: string): Json | null {
|
||||||
|
const contents = tryReadFileSync(filename);
|
||||||
|
if (contents === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return JSON.parse(contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryReadFileSync(filename: string, encoding: BufferEncoding = 'utf8'): string | null {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(fs.readFileSync(filename, 'utf8'));
|
return fs.readFileSync(filename, encoding);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.code === 'ENOENT') {
|
if (e.code === 'ENOENT') {
|
||||||
return null
|
return null
|
||||||
|
@ -261,6 +324,16 @@ function readJson(filename: string): Json | null {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tryUnlinkSync(filepath: string): void {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(filepath);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code !== 'ENOENT') {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function hash(value: Json) {
|
function hash(value: Json) {
|
||||||
const hasher = crypto.createHash('sha256');
|
const hasher = crypto.createHash('sha256');
|
||||||
hasher.update(JSON.stringify(value));
|
hasher.update(JSON.stringify(value));
|
||||||
|
@ -323,6 +396,9 @@ yargs
|
||||||
.array('exclude')
|
.array('exclude')
|
||||||
.describe('exclude', 'Files to never scan for tests')
|
.describe('exclude', 'Files to never scan for tests')
|
||||||
.default('exclude', [])
|
.default('exclude', [])
|
||||||
|
.array('alias')
|
||||||
|
.describe('alias', 'Save the test results under the given alias')
|
||||||
|
.default('alias', [])
|
||||||
, args => {
|
, args => {
|
||||||
|
|
||||||
const session = new TestSession();
|
const session = new TestSession();
|
||||||
|
@ -334,13 +410,25 @@ yargs
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const expectedKey = fs.readFileSync(path.join(STORAGE_DIR, 'aliases', 'lkg'), 'utf8')
|
for (const alias of args.alias) {
|
||||||
|
fs.mkdirpSync(path.join(STORAGE_DIR, 'aliases'))
|
||||||
|
fs.writeFileSync(path.join(STORAGE_DIR, 'aliases', alias), session.key, 'utf8')
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedKey = tryReadFileSync(path.join(STORAGE_DIR, 'aliases', 'lkg'), 'utf8');
|
||||||
|
if (expectedKey === null) {
|
||||||
|
spinner.fail(`An alias for 'lkg' was not found.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
compare(session.key, expectedKey)
|
compare(session.key, expectedKey)
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.command(['snapshot [name]'], 'Create a new snapshot from the output of the current compiler',
|
.command(['create-snapshot [alias..]'], 'Create a new snapshot from the output of the current compiler',
|
||||||
yargs => yargs
|
yargs => yargs
|
||||||
|
.array('alias')
|
||||||
|
.describe('alias', 'A user-friendly name to refer to the snapshot.')
|
||||||
|
.default('alias', [])
|
||||||
.array('include')
|
.array('include')
|
||||||
.describe('include', 'Files to scan for tests')
|
.describe('include', 'Files to scan for tests')
|
||||||
.default('include', ['test/**/*.md'])
|
.default('include', ['test/**/*.md'])
|
||||||
|
@ -355,14 +443,53 @@ yargs
|
||||||
session.run();
|
session.run();
|
||||||
session.save();
|
session.save();
|
||||||
|
|
||||||
// Set the tests that we have run as being the new 'lkg'
|
// Add any aliases that might have been requested for this snapshot
|
||||||
fs.mkdirpSync(path.join(STORAGE_DIR, 'aliases'));
|
fs.mkdirpSync(path.join(STORAGE_DIR, 'aliases'));
|
||||||
fs.writeFileSync(path.join(STORAGE_DIR, 'aliases', 'lkg'), session.key, 'utf8')
|
for (const alias of args.alias) {
|
||||||
|
fs.writeFileSync(path.join(STORAGE_DIR, 'aliases', alias), session.key, 'utf8')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.command('inspect <pattern..>', 'Inspect the ouput of a given test',
|
.command('compare [snapshot-a] [snapshot-b]', 'Compare the output of two given tests',
|
||||||
yargs => yargs
|
yargs => yargs
|
||||||
, args => {
|
, args => {
|
||||||
|
const keyA = findSnapshot(args['snapshot-a'] as string);
|
||||||
|
if (keyA === null) {
|
||||||
|
spinner.fail(`A test snapshot named '${keyA}' was not found.`)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const keyB = findSnapshot(args['snapshot-b'] as string);
|
||||||
|
if (keyB === null) {
|
||||||
|
spinner.fail(`A test snapshot named '${keyB}' was not found.`)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
compare(keyA, keyB);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.command( 'clean', 'Clean up test snapshots that are unused',
|
||||||
|
yargs => yargs
|
||||||
|
.array('keep')
|
||||||
|
.default('keep', ['lkg'])
|
||||||
|
.describe('keep', 'Keep the given aliases and anything they refer to')
|
||||||
|
, args => {
|
||||||
|
const snapshotsToKeep = new Set();
|
||||||
|
for (const alias of fs.readdirSync(path.join(STORAGE_DIR, 'aliases'))) {
|
||||||
|
if (args.keep.indexOf(alias) !== -1) {
|
||||||
|
const snapshotKey = tryReadFileSync(path.join(STORAGE_DIR, 'aliases', alias));
|
||||||
|
if (snapshotKey !== null && !fs.existsSync(path.join(STORAGE_DIR, 'snapshots', snapshotKey))) {
|
||||||
|
spinner.info(`Removing dangling alias ${alias} because the test snapshot it refers to is missing.`)
|
||||||
|
tryUnlinkSync(path.join(STORAGE_DIR, 'aliases', alias));
|
||||||
|
} else {
|
||||||
|
snapshotsToKeep.add(snapshotKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const snapshotKey of fs.readdirSync(path.join(STORAGE_DIR, 'snapshots'))) {
|
||||||
|
if (!(snapshotKey in snapshotsToKeep)) {
|
||||||
|
fs.removeSync(path.join(STORAGE_DIR, 'snapshots', snapshotKey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spinner.succeed('Cleanup complete.')
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.version()
|
.version()
|
||||||
|
|
|
@ -17,11 +17,16 @@ import {
|
||||||
FunctionBodyElement
|
FunctionBodyElement
|
||||||
} from "./ast";
|
} from "./ast";
|
||||||
import { BOLT_SUPPORTED_LANGUAGES } from "./constants"
|
import { BOLT_SUPPORTED_LANGUAGES } from "./constants"
|
||||||
import {FastStringMap, enumOr, escapeChar, assert} from "./util";
|
import { FastStringMap, enumOr, escapeChar, assert, registerClass, Newable } from "./util";
|
||||||
import { TextSpan, TextPos, TextFile } from "./text";
|
import { TextSpan, TextPos, TextFile } from "./text";
|
||||||
import { Scanner } from "./scanner";
|
import { Scanner } from "./scanner";
|
||||||
import { convertNodeToSymbolPath } from "./resolver";
|
import { convertNodeToSymbolPath } from "./resolver";
|
||||||
import { TYPE_ERROR_MESSAGES } from "./diagnostics";
|
import { TYPE_ERROR_MESSAGES } from "./diagnostics";
|
||||||
|
import { NODE_TYPES } from "./ast"
|
||||||
|
|
||||||
|
for (const key of Object.keys(NODE_TYPES)) {
|
||||||
|
registerClass((NODE_TYPES as any)[key]);
|
||||||
|
}
|
||||||
|
|
||||||
export function getSourceFile(node: Syntax) {
|
export function getSourceFile(node: Syntax) {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
|
@ -6,6 +6,8 @@ import {format, MapLike, FormatArg, countDigits, mapValues, prettyPrint, assert}
|
||||||
import { BOLT_DIAG_NUM_EXTRA_LINES } from "./constants";
|
import { BOLT_DIAG_NUM_EXTRA_LINES } from "./constants";
|
||||||
import { TextPos, TextFile, TextSpan } from "./text";
|
import { TextPos, TextFile, TextSpan } from "./text";
|
||||||
|
|
||||||
|
export const E_INVALID_TEST_COMPARE = "The given test results cannot be compared because they use different specifications."
|
||||||
|
export const E_TESTS_DO_NOT_COMPARE = "This test does not compare with its expected output."
|
||||||
export const E_NO_BOLTFILE_FOUND_IN_PATH_OR_PARENT_DIRS = 'No Boltfile found in {path} or any of its parent directories.'
|
export const E_NO_BOLTFILE_FOUND_IN_PATH_OR_PARENT_DIRS = 'No Boltfile found in {path} or any of its parent directories.'
|
||||||
export const E_SSCAN_ERROR = "Got an unexpected {char}"
|
export const E_SSCAN_ERROR = "Got an unexpected {char}"
|
||||||
export const E_STDLIB_NOT_FOUND = "Package 'stdlib' is required to build the current source set but it was not found. Use --no-std if you know what you are doing."
|
export const E_STDLIB_NOT_FOUND = "Package 'stdlib' is required to build the current source set but it was not found. Use --no-std if you know what you are doing."
|
||||||
|
@ -45,7 +47,7 @@ export interface Diagnostic {
|
||||||
message: string;
|
message: string;
|
||||||
severity: string;
|
severity: string;
|
||||||
args?: MapLike<FormatArg>;
|
args?: MapLike<FormatArg>;
|
||||||
node?: Syntax;
|
node?: { span: TextSpan | null };
|
||||||
nested?: Diagnostic[];
|
nested?: Diagnostic[];
|
||||||
position?: TextPos,
|
position?: TextPos,
|
||||||
file?: TextFile,
|
file?: TextFile,
|
||||||
|
|
27
src/text.ts
27
src/text.ts
|
@ -1,8 +1,9 @@
|
||||||
|
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import * as fs from "fs"
|
import * as fs from "fs"
|
||||||
import { serializeTag, serialize } from "./util";
|
import { serializeTag, serialize, deserializable } from "./util";
|
||||||
|
|
||||||
|
@deserializable()
|
||||||
export class TextFile {
|
export class TextFile {
|
||||||
|
|
||||||
private cachedText: string | null = null;
|
private cachedText: string | null = null;
|
||||||
|
@ -16,7 +17,7 @@ export class TextFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
[serializeTag]() {
|
[serializeTag]() {
|
||||||
return this.origPath;
|
return [ this.origPath ];
|
||||||
}
|
}
|
||||||
|
|
||||||
public getText(encoding: BufferEncoding = 'utf8'): string {
|
public getText(encoding: BufferEncoding = 'utf8'): string {
|
||||||
|
@ -30,6 +31,7 @@ export class TextFile {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@deserializable()
|
||||||
export class TextPos {
|
export class TextPos {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -45,11 +47,11 @@ export class TextPos {
|
||||||
}
|
}
|
||||||
|
|
||||||
[serializeTag]() {
|
[serializeTag]() {
|
||||||
return {
|
return [
|
||||||
offset: this.offset,
|
this.offset,
|
||||||
line: this.line,
|
this.line,
|
||||||
column: this.column,
|
this.column,
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
public advance(str: string) {
|
public advance(str: string) {
|
||||||
|
@ -66,6 +68,7 @@ export class TextPos {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@deserializable()
|
||||||
export class TextSpan {
|
export class TextSpan {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -81,11 +84,11 @@ export class TextSpan {
|
||||||
}
|
}
|
||||||
|
|
||||||
[serializeTag]() {
|
[serializeTag]() {
|
||||||
return {
|
return [
|
||||||
file: serialize(this.file),
|
this.file,
|
||||||
start: serialize(this.start),
|
this.start,
|
||||||
end: serialize(this.end),
|
this.end,
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
191
src/util.ts
191
src/util.ts
|
@ -6,6 +6,7 @@ import * as os from "os"
|
||||||
import moment from "moment"
|
import moment from "moment"
|
||||||
import chalk from "chalk"
|
import chalk from "chalk"
|
||||||
import { LOG_DATETIME_FORMAT } from "./constants"
|
import { LOG_DATETIME_FORMAT } from "./constants"
|
||||||
|
import { NODE_TYPES } from "./ast"
|
||||||
|
|
||||||
export function isPowerOf(x: number, n: number):boolean {
|
export function isPowerOf(x: number, n: number):boolean {
|
||||||
const a = Math.log(x) / Math.log(n);
|
const a = Math.log(x) / Math.log(n);
|
||||||
|
@ -196,44 +197,45 @@ export function isPrimitive(value: any): boolean {
|
||||||
return (typeof(value) !== 'function' && typeof(value) !== 'object') || value === null;
|
return (typeof(value) !== 'function' && typeof(value) !== 'object') || value === null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const serializeTag = Symbol('serializer tag');
|
export const serializeTag = Symbol('serialize tag');
|
||||||
|
export const deserializeTag = Symbol('deserialize tag');
|
||||||
|
|
||||||
const serializableClasses = new Map<string, Newable<{ [serializeTag](): Json }>>();
|
const deserializableClasses = new Map<string, Newable<{ [serializeTag](): Json }>>();
|
||||||
|
|
||||||
|
export function registerClass(cls: Newable<any>) {
|
||||||
|
deserializableClasses.set(cls.name, cls)
|
||||||
|
}
|
||||||
|
|
||||||
|
const TYPE_KEY = '__type'
|
||||||
|
|
||||||
export function serialize(value: any): Json {
|
export function serialize(value: any): Json {
|
||||||
if (isPrimitive(value)) {
|
if (isPrimitive(value)) {
|
||||||
if (typeof(value) === 'bigint') {
|
if (typeof(value) === 'bigint') {
|
||||||
return {
|
return {
|
||||||
type: 'bigint',
|
[TYPE_KEY]: 'bigint',
|
||||||
value: value.toString(),
|
value: value.toString(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return value;
|
||||||
type: 'primitive',
|
|
||||||
value,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (Array.isArray(value)) {
|
} else if (Array.isArray(value)) {
|
||||||
return {
|
return value.map(serialize);
|
||||||
type: 'array',
|
|
||||||
elements: value.map(serialize)
|
|
||||||
}
|
|
||||||
} else if (isObjectLike(value)) {
|
} else if (isObjectLike(value)) {
|
||||||
if (isPlainObject(value)) {
|
if (isPlainObject(value)) {
|
||||||
return {
|
const result: MapLike<Json> = {};
|
||||||
type: 'object',
|
for (const key of Object.keys(value)) {
|
||||||
elements: [...map(values(value), element => serialize(element))]
|
result[key] = serialize(value[key]);
|
||||||
};
|
}
|
||||||
|
return result;
|
||||||
} else if (value[serializeTag] !== undefined
|
} else if (value[serializeTag] !== undefined
|
||||||
&& typeof(value[serializeTag]) === 'function'
|
&& typeof(value[serializeTag]) === 'function'
|
||||||
&& typeof(value.constructor.name) === 'string') {
|
&& typeof(value.constructor.name) === 'string') {
|
||||||
return {
|
return {
|
||||||
type: 'class',
|
[TYPE_KEY]: 'classinstance',
|
||||||
name: value.constructor.name,
|
name: value.constructor.name,
|
||||||
data: value[serializeTag](),
|
args: value[serializeTag]().map(serialize),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(value);
|
|
||||||
throw new Error(`Could not serialize ${value}: it was a non-primitive object and has no serializer tag.`)
|
throw new Error(`Could not serialize ${value}: it was a non-primitive object and has no serializer tag.`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -241,69 +243,103 @@ export function serialize(value: any): Json {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TransparentProxy<T> = T & { updateHandle(value: T): void }
|
export function deserialize(data: Json): any {
|
||||||
|
if (isPrimitive(data)) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data.map(deserialize);
|
||||||
|
}
|
||||||
|
if (isPlainObject(data)) {
|
||||||
|
if (data[TYPE_KEY] === 'bigint') {
|
||||||
|
return BigInt(data.value);
|
||||||
|
}
|
||||||
|
if (data[TYPE_KEY] === 'classinstance') {
|
||||||
|
const cls = deserializableClasses.get(data.name as string);
|
||||||
|
if (cls === undefined) {
|
||||||
|
throw new Error(`Could not deserialize ${data.name}: class not found.`)
|
||||||
|
}
|
||||||
|
const args = (data.args as JsonArray).map(deserialize);
|
||||||
|
return new cls(...args)
|
||||||
|
}
|
||||||
|
const result: MapLike<any> = {};
|
||||||
|
for (const key of Object.keys(data)) {
|
||||||
|
result[key] = deserialize(data[key]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
throw new Error(`I did not know how to deserialize ${data}'.`)
|
||||||
|
}
|
||||||
|
|
||||||
export function createTransparentProxy<T extends object>(value: T): TransparentProxy<T> {
|
export function deserializable() {
|
||||||
const handlerObject = {
|
return function (target: any) {
|
||||||
__HANDLE: value,
|
deserializableClasses.set(target.name, target);
|
||||||
__IS_HANDLE: true,
|
|
||||||
updateHandle(newValue: T) {
|
|
||||||
if (newValue.__IS_HANDLE) {
|
|
||||||
newValue = newValue.__HANDLE;
|
|
||||||
}
|
}
|
||||||
value = newValue;
|
|
||||||
handlerObject.__HANDLE = newValue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return new Proxy<any>({}, {
|
|
||||||
getPrototypeOf(target: T): object | null {
|
|
||||||
return Reflect.getPrototypeOf(value);
|
|
||||||
},
|
|
||||||
setPrototypeOf(target: T, v: any): boolean {
|
|
||||||
return Reflect.setPrototypeOf(value, v);
|
|
||||||
},
|
|
||||||
isExtensible(target: T): boolean {
|
|
||||||
return Reflect.isExtensible(value);
|
|
||||||
},
|
|
||||||
preventExtensions(target: T): boolean {
|
|
||||||
return Reflect.preventExtensions(value);
|
|
||||||
},
|
|
||||||
getOwnPropertyDescriptor(target: T, p: PropertyKey): PropertyDescriptor | undefined {
|
|
||||||
return Reflect.getOwnPropertyDescriptor(value, p);
|
|
||||||
},
|
|
||||||
has(target: T, p: PropertyKey): boolean {
|
|
||||||
return Reflect.has(value, p);
|
|
||||||
},
|
|
||||||
get(target: T, p: PropertyKey, receiver: any): any {
|
|
||||||
if (hasOwnProperty(handlerObject, p)) {
|
|
||||||
return Reflect.get(handlerObject, p);
|
|
||||||
}
|
|
||||||
return Reflect.get(value, p, receiver)
|
|
||||||
},
|
|
||||||
set(target: T, p: PropertyKey, value: any, receiver: any): boolean {
|
|
||||||
return Reflect.set(value, p, value);
|
|
||||||
},
|
|
||||||
deleteProperty(target: T, p: PropertyKey): boolean {
|
|
||||||
return Reflect.deleteProperty(value, p);
|
|
||||||
},
|
|
||||||
defineProperty(target: T, p: PropertyKey, attributes: PropertyDescriptor): boolean {
|
|
||||||
return Reflect.defineProperty(value, p, attributes);
|
|
||||||
},
|
|
||||||
enumerate(target: T): PropertyKey[] {
|
|
||||||
return [...Reflect.enumerate(value)];
|
|
||||||
},
|
|
||||||
ownKeys(target: T): PropertyKey[] {
|
|
||||||
return Reflect.ownKeys(value);
|
|
||||||
},
|
|
||||||
apply(target: T, thisArg: any, argArray?: any): any {
|
|
||||||
return Reflect.apply(value as any, thisArg, argArray);
|
|
||||||
},
|
|
||||||
construct(target: T, argArray: any, newTarget?: any): object {
|
|
||||||
return Reflect.construct(value as any, argArray, newTarget);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//export type TransparentProxy<T> = T & { updateHandle(value: T): void }
|
||||||
|
|
||||||
|
//export function createTransparentProxy<T extends object>(value: T): TransparentProxy<T> {
|
||||||
|
// const handlerObject = {
|
||||||
|
// __HANDLE: value,
|
||||||
|
// __IS_HANDLE: true,
|
||||||
|
// updateHandle(newValue: T) {
|
||||||
|
// if (newValue.__IS_HANDLE) {
|
||||||
|
// newValue = newValue.__HANDLE;
|
||||||
|
// }
|
||||||
|
// value = newValue;
|
||||||
|
// handlerObject.__HANDLE = newValue;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// return new Proxy<any>({}, {
|
||||||
|
// getPrototypeOf(target: T): object | null {
|
||||||
|
// return Reflect.getPrototypeOf(value);
|
||||||
|
// },
|
||||||
|
// setPrototypeOf(target: T, v: any): boolean {
|
||||||
|
// return Reflect.setPrototypeOf(value, v);
|
||||||
|
// },
|
||||||
|
// isExtensible(target: T): boolean {
|
||||||
|
// return Reflect.isExtensible(value);
|
||||||
|
// },
|
||||||
|
// preventExtensions(target: T): boolean {
|
||||||
|
// return Reflect.preventExtensions(value);
|
||||||
|
// },
|
||||||
|
// getOwnPropertyDescriptor(target: T, p: PropertyKey): PropertyDescriptor | undefined {
|
||||||
|
// return Reflect.getOwnPropertyDescriptor(value, p);
|
||||||
|
// },
|
||||||
|
// has(target: T, p: PropertyKey): boolean {
|
||||||
|
// return Reflect.has(value, p);
|
||||||
|
// },
|
||||||
|
// get(target: T, p: PropertyKey, receiver: any): any {
|
||||||
|
// if (hasOwnProperty(handlerObject, p)) {
|
||||||
|
// return Reflect.get(handlerObject, p);
|
||||||
|
// }
|
||||||
|
// return Reflect.get(value, p, receiver)
|
||||||
|
// },
|
||||||
|
// set(target: T, p: PropertyKey, value: any, receiver: any): boolean {
|
||||||
|
// return Reflect.set(value, p, value);
|
||||||
|
// },
|
||||||
|
// deleteProperty(target: T, p: PropertyKey): boolean {
|
||||||
|
// return Reflect.deleteProperty(value, p);
|
||||||
|
// },
|
||||||
|
// defineProperty(target: T, p: PropertyKey, attributes: PropertyDescriptor): boolean {
|
||||||
|
// return Reflect.defineProperty(value, p, attributes);
|
||||||
|
// },
|
||||||
|
// enumerate(target: T): PropertyKey[] {
|
||||||
|
// return [...Reflect.enumerate(value)];
|
||||||
|
// },
|
||||||
|
// ownKeys(target: T): PropertyKey[] {
|
||||||
|
// return Reflect.ownKeys(value);
|
||||||
|
// },
|
||||||
|
// apply(target: T, thisArg: any, argArray?: any): any {
|
||||||
|
// return Reflect.apply(value as any, thisArg, argArray);
|
||||||
|
// },
|
||||||
|
// construct(target: T, argArray: any, newTarget?: any): object {
|
||||||
|
// return Reflect.construct(value as any, argArray, newTarget);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//}
|
||||||
|
|
||||||
export const getKeyTag = Symbol('get key of object');
|
export const getKeyTag = Symbol('get key of object');
|
||||||
|
|
||||||
function getKey(value: any): string {
|
function getKey(value: any): string {
|
||||||
|
@ -648,6 +684,7 @@ export function format(message: string, data: MapLike<FormatArg>) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function deepEqual(a: any, b: any): boolean {
|
export function deepEqual(a: any, b: any): boolean {
|
||||||
if (isPrimitive(a) && isPrimitive(b)) {
|
if (isPrimitive(a) && isPrimitive(b)) {
|
||||||
return a === b;
|
return a === b;
|
||||||
|
|
Loading…
Reference in a new issue