Skip to content

Commit d3aa5fa

Browse files
authored
refactor(core): share op prototypes and keep the es5 bundles es5 (#352)
- Build the to, is, has, deep, own, nested, strictly and numeric op tables once and share one prototype per table, instead of redefining 30 to 50 getters on every chain step. IAssertScopeFuncDef moves out of IAssertInst.ts into its own file for the shared tables to use. - The rebuilt tables also put equal, equals and eq on to and add Chai's at, so expect(5).to.be.at.least(5) works. - Replace bare Symbol.iterator and Error.captureStackTrace uses with ts-utils helpers or casts so the es5 bundles stay es5, and print accessor properties as [Getter] instead of calling them. - The default config sets timeout, slow and retries to 2000, 75 and 0 for the suite runner.
1 parent 7c27ebe commit d3aa5fa

52 files changed

Lines changed: 1053 additions & 404 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎core/src/assert/adapters/evalAdapter.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
* @nevware21/tripwire
33
* https://github.com/nevware21/tripwire
44
*
5-
* Copyright (c) 2024-2025 NevWare21 Solutions LLC
5+
* Copyright (c) 2024-2026 NevWare21 Solutions LLC
66
* Licensed under the MIT license.
77
*/
88

99
import { arrSlice } from "@nevware21/ts-utils";
10-
import { IAssertScope } from "../interface/IAssertScope";
10+
import { _blockLength } from "../internal/_blockLength";
1111
import { MsgSource } from "../../types/MsgSource";
1212
import { IScopeFn } from "../interface/IScopeFuncs";
13-
import { _blockLength } from "../internal/_blockLength";
13+
import { IAssertScope } from "../interface/IAssertScope";
1414

1515
/**
1616
* Identifies the signature of a function that expects the actual value and any additional arguments

‎core/src/assert/assertInst.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
* Licensed under the MIT license.
77
*/
88

9-
import { IAssertScopeFuncDef, AssertScopeFuncDefs } from "./interface/IAssertInst";
9+
import { MsgSource } from "../types/MsgSource";
10+
import { AssertScopeFuncDefs, IAssertScopeFuncDef } from "./interface/IAssertScopeFuncDef";
1011
import { IScopeFn, IScopePropFn } from "./interface/IScopeFuncs";
1112
import { _addAssertInstFuncs } from "./internal/_addAssertInstFuncs";
1213
import { _getAssertProto } from "./internal/_AssertInstCore";
13-
import { MsgSource } from "../types/MsgSource";
1414

1515
/**
1616
* Adds a set of scope functions to the assert (instance) prototype which

‎core/src/assert/assertScope.ts‎

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,20 @@
66
* Licensed under the MIT license.
77
*/
88

9-
import { arrSlice, objDefine } from "@nevware21/ts-utils";
10-
import { IScopeContext, IScopeContextOverrides } from "./interface/IScopeContext";
11-
import { AssertScopeFuncDefs } from "./interface/IAssertInst";
12-
import { MsgSource } from "../types/MsgSource";
13-
import { EXEC } from "./internal/const";
9+
import { arrSlice, newSymbol, objCreate, objDefine, objIsExtensible } from "@nevware21/ts-utils";
1410
import { IAssertScope } from "./interface/IAssertScope";
1511
import { _addAssertInstFuncs } from "./internal/_addAssertInstFuncs";
1612
import { IScopeFn } from "./interface/IScopeFuncs";
1713
import { AssertInstHandlers } from "./interface/IAssertInstHandlers";
18-
import { _createLazyInstHandler, _setAssertScope } from "./internal/_instCreator";
14+
import { _createLazyInstHandler, _createOpInst, _setAssertScope, _tagAssertInst } from "./internal/_instCreator";
15+
import { _getEmptyAssertProto } from "./internal/_AssertInstCore";
1916
import { _tripwireAssertHandlers } from "./internal/_tripwireInst";
2017
import { _failFn, _fatalFn } from "./internal/_failFn";
2118
import { useScope } from "./useScope";
19+
import { MsgSource } from "../types/MsgSource";
20+
import { AssertScopeFuncDefs } from "./interface/IAssertScopeFuncDef";
21+
import { IScopeContext, IScopeContextOverrides } from "./interface/IScopeContext";
22+
import { EXEC } from "../internal/const";
2223

2324

2425
/**
@@ -30,9 +31,44 @@ import { useScope } from "./useScope";
3031
* @group Scope
3132
* @since 0.1.0
3233
*/
34+
const _opProtoTag = /* #__PURE__ */ newSymbol("@nevware21/tripwire#opProto");
35+
36+
/**
37+
* Returns the prototype shared by every operation instance created for the given `funcs` definition
38+
* table, creating (and caching it on the table itself) the first time the table is seen. The operation
39+
* getters resolve their scope from the instance they are invoked on, so they only need to be defined
40+
* once per table rather than on every instance -- which previously cost 30 to 50 property definitions
41+
* for each `.to`, `.be`, `.is`, `.deep` (etc.) step of an assertion chain.
42+
*
43+
* The table is bound the first time it is used: a definition changed or added afterwards is not seen by
44+
* later operations, so a table passed to `createOperation` is treated as immutable from then on. A frozen,
45+
* sealed or otherwise non-extensible table cannot carry the cache, so it gets a fresh prototype per call --
46+
* the cost it had before the cache existed -- rather than a TypeError from defining the cache property.
47+
* @internal
48+
*/
49+
function _getOpProto<T>(funcs: AssertScopeFuncDefs<T>, internalErrorStackStart: Function): any {
50+
let proto = (funcs as any)[_opProtoTag];
51+
if (!proto) {
52+
proto = objCreate(_getEmptyAssertProto());
53+
_tagAssertInst(proto);
54+
_addAssertInstFuncs(proto, funcs, false, internalErrorStackStart);
55+
if (objIsExtensible(funcs)) {
56+
objDefine(funcs as any, _opProtoTag, {
57+
v: proto,
58+
e: false
59+
});
60+
}
61+
}
62+
63+
return proto;
64+
}
65+
3366
export function createAssertScope(context: IScopeContext, handlerCreator?: AssertInstHandlers): IAssertScope {
3467
let _context: IScopeContext = context;
3568
let creators = _createLazyInstHandler(handlerCreator || _tripwireAssertHandlers);
69+
// The shared prototype fast path derives from the default empty instance, so it is only used when
70+
// the default instance handlers are in effect.
71+
let _useSharedProto = !handlerCreator || handlerCreator === _tripwireAssertHandlers;
3672

3773
let theScope: IAssertScope = {
3874
context: _context,
@@ -143,6 +179,12 @@ export function createAssertScope(context: IScopeContext, handlerCreator?: Asser
143179

144180
function createOperation<T>(funcs: AssertScopeFuncDefs<T>, obj?: any): T {
145181
if (!obj) {
182+
if (_useSharedProto) {
183+
// Fast path: the operation functions live on a prototype shared by every instance
184+
// created for this `funcs` table, so only the scope binding is per-instance.
185+
return _createOpInst(_getOpProto(funcs, createOperation), theScope);
186+
}
187+
146188
obj = newEmptyInst();
147189
} else {
148190
// Make sure that the obj is tagged with the current scope so that

‎core/src/assert/funcs/hasProperty.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
*/
88

99
import { arrForEach, isArray, isNumber, isObject, isPrimitive, isStrictNullOrUndefined, isString, isSymbol, objGetOwnPropertyDescriptor, objGetPrototypeOf, objHasOwnProperty } from "@nevware21/ts-utils";
10-
import { MsgSource } from "../../types/MsgSource";
11-
import { IScopeContext } from "../interface/IScopeContext";
1210
import { deepEqualsFunc, equalsFunc } from "./equal";
13-
import { IAssertScope } from "../interface/IAssertScope";
1411
import { IPropertyResultOp } from "../interface/ops/IPropertyResultOp";
1512
import { propertyResultOp } from "../ops/propertyResultOp";
13+
import { MsgSource } from "../../types/MsgSource";
14+
import { IAssertScope } from "../interface/IAssertScope";
15+
import { IScopeContext } from "../interface/IScopeContext";
1616

1717
function _getPropertyDescriptor<T>(target: T, name: string | symbol | number): PropertyDescriptor | undefined {
1818
let currentObj = target;

‎core/src/assert/funcs/isExtensible.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
* @nevware21/tripwire
33
* https://github.com/nevware21/tripwire
44
*
5-
* Copyright (c) 2024-2025 NevWare21 Solutions LLC
5+
* Copyright (c) 2024-2026 NevWare21 Solutions LLC
66
* Licensed under the MIT license.
77
*/
88

9-
import { MsgSource } from "../../types/MsgSource";
10-
import { IAssertScope } from "../interface/IAssertScope";
119
import { objIsExtensible } from "@nevware21/ts-utils";
10+
import { IAssertScope } from "../interface/IAssertScope";
11+
import { MsgSource } from "../../types/MsgSource";
1212

1313
export function isExtensibleFunc<R>(this: IAssertScope, evalMsg?: MsgSource): R {
1414
let scope = this;
@@ -19,4 +19,4 @@ export function isExtensibleFunc<R>(this: IAssertScope, evalMsg?: MsgSource): R
1919
context.eval(value === Object(value) && objIsExtensible(value), evalMsg || "expected {value} to be extensible");
2020

2121
return scope.that;
22-
}
22+
}

‎core/src/assert/funcs/isFrozen.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22
* @nevware21/tripwire
33
* https://github.com/nevware21/tripwire
44
*
5-
* Copyright (c) 2024-2025 NevWare21 Solutions LLC
5+
* Copyright (c) 2024-2026 NevWare21 Solutions LLC
66
* Licensed under the MIT license.
77
*/
88

99
import { objIsFrozen } from "@nevware21/ts-utils";
10-
import { IAssertScope } from "../interface/IAssertScope";
1110
import { MsgSource } from "../../types/MsgSource";
11+
import { IAssertScope } from "../interface/IAssertScope";
1212

1313
export function isFrozenFunc<R>(this: IAssertScope, evalMsg?: MsgSource): R{
1414
let scope = this;
1515
let context = scope.context;
1616
context.eval(objIsFrozen(context.value), evalMsg || "expected {value} to be frozen");
1717

1818
return scope.that;
19-
}
19+
}

‎core/src/assert/funcs/isSealed.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22
* @nevware21/tripwire
33
* https://github.com/nevware21/tripwire
44
*
5-
* Copyright (c) 2024-2025 NevWare21 Solutions LLC
5+
* Copyright (c) 2024-2026 NevWare21 Solutions LLC
66
* Licensed under the MIT license.
77
*/
88

9-
import { MsgSource } from "../../types/MsgSource";
10-
import { IAssertScope } from "../interface/IAssertScope";
119
import { objIsSealed } from "@nevware21/ts-utils";
10+
import { IAssertScope } from "../interface/IAssertScope";
11+
import { MsgSource } from "../../types/MsgSource";
1212

1313
export function isSealedFunc<R>(this: IAssertScope, evalMsg?: MsgSource): R {
1414
let scope = this;
1515
let context = scope.context;
1616
context.eval(objIsSealed(context.value), evalMsg || "expected {value} to be sealed");
1717

1818
return scope.that;
19-
}
19+
}

‎core/src/assert/funcs/members.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
*/
88

99
import { arrForEach, arrFrom } from "@nevware21/ts-utils";
10-
import { IAssertScope } from "../interface/IAssertScope";
11-
import { MsgSource } from "../../types/MsgSource";
12-
import { ArrayLikeOrSizedIterable } from "../../types/ArrayLikeOrSizedIterable";
1310
import { _deepEqual } from "./equal";
1411
import { _getArrayLikeOrIterableSize, _isArrayLikeOrIterable, _iterateForEachItem } from "../internal/_isArrayLikeOrIterable";
12+
import { MsgSource } from "../../types/MsgSource";
13+
import { ArrayLikeOrSizedIterable } from "../../types/ArrayLikeOrSizedIterable";
14+
import { IAssertScope } from "../interface/IAssertScope";
1515
import { IScopeContext } from "../interface/IScopeContext";
1616

1717
function _checkActualAndExpected(context: IScopeContext, value: any, expected: any, evalMsg?: MsgSource): void {

‎core/src/assert/funcs/throws.ts‎

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
* @nevware21/tripwire
33
* https://github.com/nevware21/tripwire
44
*
5-
* Copyright (c) 2024-2025 NevWare21 Solutions LLC
5+
* Copyright (c) 2024-2026 NevWare21 Solutions LLC
66
* Licensed under the MIT license.
77
*/
88

99
import { isError, isFunction, isObject, isRegExp, isStrictNullOrUndefined, isString, strIndexOf } from "@nevware21/ts-utils";
1010
import { MsgSource } from "../../types/MsgSource";
1111
import { IAssertScope } from "../interface/IAssertScope";
1212

13-
const enum MatchError {
13+
const enum eMatchError {
1414
Nothing = 0x00,
1515
ErrorInstance = 0x01,
1616
ErrorType = 0x02,
@@ -22,28 +22,28 @@ const enum MatchError {
2222
interface IErrorMatch {
2323
result: boolean;
2424
errorLike?: ErrorConstructor | Error;
25-
matchError: MatchError;
25+
matchError: eMatchError;
2626
}
2727

2828
const cErrorFnErrors: { [key: number]: MsgSource } = {
29-
[MatchError.Nothing]: "expected {value} to be an Error instance",
30-
[MatchError.ErrorInstance]: "expected {error} to be an instance of {errorLike}",
31-
[MatchError.ErrorType]: "expected {error} to be of {errorLike} type"
29+
[eMatchError.Nothing]: "expected {value} to be an Error instance",
30+
[eMatchError.ErrorInstance]: "expected {error} to be an instance of {errorLike}",
31+
[eMatchError.ErrorType]: "expected {error} to be of {errorLike} type"
3232
};
3333

34-
const cThrowsErrors: { [key: number]: MsgSource } = {
35-
[MatchError.Nothing]: null,
36-
[MatchError.ErrorInstance]: "expected {function} to throw a {errorLike} instance but {error} was thrown",
37-
[MatchError.ErrorType]: "expected {function} to throw an error of type {errorLike} but threw {error}",
38-
[MatchError.MessageContains]: "expected {function} to throw an error with a message containing {msgMatch} but it was {message}",
39-
[MatchError.MessageMatches]: "expected {function} to throw an error with a message matching {msgMatch} but it was {message}",
40-
[MatchError.MessageEquals]: "expected {function} to throw an error with a message equal to {msgMatch} but it was {message}",
41-
[MatchError.ErrorInstance | MatchError.MessageContains]: "expected {function} to throw a {errorLike} instance with a message containing {msgMatch} but {error} was thrown",
42-
[MatchError.ErrorInstance | MatchError.MessageMatches]: "expected {function} to throw a {errorLike} instance with a message matching {msgMatch} but {error} was thrown",
43-
[MatchError.ErrorInstance | MatchError.MessageEquals]: "expected {function} to throw a {errorLike} instance with a message equal to {msgMatch} but {error} was thrown",
44-
[MatchError.ErrorType | MatchError.MessageContains]: "expected {function} to throw an error of type {errorLike} with a message containing {msgMatch} but {error} was thrown",
45-
[MatchError.ErrorType | MatchError.MessageMatches]: "expected {function} to throw an error of type {errorLike} with a message matching {msgMatch} but {error} was thrown",
46-
[MatchError.ErrorType | MatchError.MessageEquals]: "expected {function} to throw an error of type {errorLike} with a message equal to {msgMatch} but {error} was thrown"
34+
const cThrowsErrors: { [key: number]: MsgSource | null } = {
35+
[eMatchError.Nothing]: null,
36+
[eMatchError.ErrorInstance]: "expected {function} to throw a {errorLike} instance but {error} was thrown",
37+
[eMatchError.ErrorType]: "expected {function} to throw an error of type {errorLike} but threw {error}",
38+
[eMatchError.MessageContains]: "expected {function} to throw an error with a message containing {msgMatch} but it was {message}",
39+
[eMatchError.MessageMatches]: "expected {function} to throw an error with a message matching {msgMatch} but it was {message}",
40+
[eMatchError.MessageEquals]: "expected {function} to throw an error with a message equal to {msgMatch} but it was {message}",
41+
[eMatchError.ErrorInstance | eMatchError.MessageContains]: "expected {function} to throw a {errorLike} instance with a message containing {msgMatch} but {error} was thrown",
42+
[eMatchError.ErrorInstance | eMatchError.MessageMatches]: "expected {function} to throw a {errorLike} instance with a message matching {msgMatch} but {error} was thrown",
43+
[eMatchError.ErrorInstance | eMatchError.MessageEquals]: "expected {function} to throw a {errorLike} instance with a message equal to {msgMatch} but {error} was thrown",
44+
[eMatchError.ErrorType | eMatchError.MessageContains]: "expected {function} to throw an error of type {errorLike} with a message containing {msgMatch} but {error} was thrown",
45+
[eMatchError.ErrorType | eMatchError.MessageMatches]: "expected {function} to throw an error of type {errorLike} with a message matching {msgMatch} but {error} was thrown",
46+
[eMatchError.ErrorType | eMatchError.MessageEquals]: "expected {function} to throw an error of type {errorLike} with a message equal to {msgMatch} but {error} was thrown"
4747
};
4848

4949
function _isError(value: any): value is Error {
@@ -142,9 +142,9 @@ export function isErrorFunc<R>(this: IAssertScope, errorLike?: ErrorConstructor
142142
if (errorLike) {
143143
let errorLikeResult = _isErrorLikeCompatible(errorValue, errorLike);
144144

145-
context.eval(errorLikeResult.result, evalMsg || cErrorFnErrors[errorLikeResult.matchError] || cErrorFnErrors[MatchError.Nothing]);
145+
context.eval(errorLikeResult.result, evalMsg || cErrorFnErrors[errorLikeResult.matchError] || cErrorFnErrors[eMatchError.Nothing]);
146146
} else {
147-
context.eval(_isError(errorValue), evalMsg || cErrorFnErrors[MatchError.Nothing]);
147+
context.eval(_isError(errorValue), evalMsg || cErrorFnErrors[eMatchError.Nothing]);
148148
}
149149

150150
return this.that;
@@ -168,7 +168,7 @@ function _isErrorLikeCompatible(errorValue: any, errorLike: ErrorConstructor | E
168168
return {
169169
result: errorValue && (errorValue.constructor instanceof errError.constructor || errorValue instanceof errError.constructor),
170170
errorLike: errorLike,
171-
matchError: MatchError.ErrorInstance
171+
matchError: eMatchError.ErrorInstance
172172
};
173173
}
174174

@@ -177,22 +177,22 @@ function _isErrorLikeCompatible(errorValue: any, errorLike: ErrorConstructor | E
177177
return {
178178
result: errorValue && (errorValue.constructor === errType || errorValue instanceof errType),
179179
errorLike: errorLike,
180-
matchError: MatchError.ErrorType
180+
matchError: eMatchError.ErrorType
181181
};
182182
//, evalMsg || "expected {value} to be of {errorLike} type.");
183183
}
184184

185185
return {
186186
result: errorValue instanceof errType,
187187
errorLike: errorLike,
188-
matchError: MatchError.ErrorType
188+
matchError: eMatchError.ErrorType
189189
};
190190
}
191191

192192
// We where not given a specific error to compare to, so just check if the value is an error
193193
return {
194194
result: true,
195-
matchError: MatchError.Nothing
195+
matchError: eMatchError.Nothing
196196
};
197197
//, evalMsg || "expected {value} to be an Error instance");
198198
}
@@ -202,26 +202,26 @@ function _isErrorMessageCompatible(errorValue: Error, msgMatch: string | RegExp)
202202
if (isString(msgMatch)) {
203203
return {
204204
result: message == msgMatch || strIndexOf(message, msgMatch) >= 0,
205-
matchError: MatchError.MessageContains
205+
matchError: eMatchError.MessageContains
206206
};
207207
}
208208

209209
if (isRegExp(msgMatch)) {
210210
return {
211211
result: msgMatch.test(message),
212-
matchError: MatchError.MessageMatches
212+
matchError: eMatchError.MessageMatches
213213
};
214214
}
215215

216216
if (msgMatch) {
217217
return {
218218
result: msgMatch == message,
219-
matchError: MatchError.MessageEquals
219+
matchError: eMatchError.MessageEquals
220220
};
221221
}
222222

223223
return {
224224
result: true,
225-
matchError: MatchError.Nothing
225+
matchError: eMatchError.Nothing
226226
};
227-
}
227+
}

‎core/src/assert/funcs/valuesFunc.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import {
1111
isStrictNullOrUndefined, isString, iterForOf, objForEachKey, strIndexOf
1212
} from "@nevware21/ts-utils";
1313
import { IAssertScope } from "../interface/IAssertScope";
14-
import { EMPTY_STRING } from "../internal/const";
1514
import { ValuesFn } from "../interface/funcs/ValuesFn";
15+
import { EMPTY_STRING } from "../../internal/const";
1616

1717
function _getCtxValues(scope: IAssertScope, value: any): any[] {
1818
let theValues: any[] = [];

0 commit comments

Comments
 (0)