Skip to content

Commit 436d4b7

Browse files
authored
feat(core): add optimized createNotAdapter for negation operations (#250)
Add a new `createNotAdapter` function that wraps scope functions with a "not" operation more efficiently than `createExprAdapter("not", scopeFn)`. This avoids expression parsing overhead by directly applying the negation context via `notOp`. Changes: - Add core/src/assert/adapters/notAdapter.ts - Replace all 47 usages of `createExprAdapter("not", fn)` with `createNotAdapter(fn)` in assertClass.ts - Export `createNotAdapter` from the package index
1 parent 91c7753 commit 436d4b7

File tree

4 files changed

+801
-56
lines changed

4 files changed

+801
-56
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* @nevware21/tripwire
3+
* https://github.com/nevware21/tripwire
4+
*
5+
* Copyright (c) 2026 NevWare21 Solutions LLC
6+
* Licensed under the MIT license.
7+
*/
8+
9+
import { arrSlice } from "@nevware21/ts-utils";
10+
import { IAssertScope } from "../interface/IAssertScope";
11+
import { IScopeFn } from "../interface/IScopeFuncs";
12+
import { notOp } from "../ops/notOp";
13+
import { _blockLength } from "../internal/_blockLength";
14+
15+
/**
16+
* Creates an adapter function that wraps the provided scope function with a "not" operation.
17+
* When the returned scope function is executed, it first performs a negation (updating the
18+
* context to negate evaluations) and then calls the provided scope function.
19+
*
20+
* This is an optimized alternative to using `createExprAdapter("not", scopeFn)` that avoids
21+
* expression parsing overhead by directly applying the negation context.
22+
*
23+
* @param scopeFn - The scope function to wrap with the "not" operation.
24+
* @returns An {@link IScopeFn} function that will negate and then execute the provided scope function.
25+
* @example
26+
* ```typescript
27+
* import { createNotAdapter, createEvalAdapter } from "@nevware21/tripwire";
28+
*
29+
* const isTrueFunc = createEvalAdapter((actual: any) => actual === true, "Expected value to be true");
30+
* const isNotTrueFunc = createNotAdapter(isTrueFunc);
31+
*
32+
* // Using with addAssertFunc
33+
* addAssertFunc("isNotTrue", isNotTrueFunc);
34+
* assert.isNotTrue(false); // Passes - false is not true
35+
* assert.isNotTrue(true); // Fails - true is true
36+
* ```
37+
* @group Adapter
38+
* @since 0.1.5
39+
*/
40+
export function createNotAdapter(scopeFn: IScopeFn): IScopeFn {
41+
42+
function _notFn(this: IAssertScope): any {
43+
let scope = this;
44+
let context = scope.context;
45+
let theArgs = arrSlice(arguments);
46+
47+
context._$stackFn.push(_notFn);
48+
49+
// Track the operation path and set the stack start position
50+
if (context.opts.isVerbose) {
51+
context.setOp("[[not]]");
52+
}
53+
54+
// Apply the "not" operation
55+
notOp(scope);
56+
57+
// Execute the scope function within the negated context
58+
return scope.exec(scopeFn, theArgs);
59+
}
60+
61+
62+
return _blockLength(_notFn, scopeFn.name, [], _notFn);
63+
}

core/src/assert/assertClass.ts

Lines changed: 56 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { createAssertScope } from "./assertScope";
2727
import { assertConfig } from "./config";
2828
import { IScopeFn } from "./interface/IScopeFuncs";
2929
import { createExprAdapter } from "./adapters/exprAdapter";
30+
import { createNotAdapter } from "./adapters/notAdapter";
3031
import { isSealedFunc } from "./funcs/isSealed";
3132
import { isFrozenFunc } from "./funcs/isFrozen";
3233
import { isExtensibleFunc } from "./funcs/isExtensible";
@@ -163,45 +164,45 @@ export function createAssert(): IAssertClass {
163164
equals: { alias: "equal" },
164165
strictEqual: { scopeFn: strictEqualsFunc, nArgs: 2 },
165166
strictEquals: { alias: "strictEqual" },
166-
notStrictEqual: { scopeFn: createExprAdapter("not", strictEqualsFunc), nArgs: 2 },
167+
notStrictEqual: { scopeFn: createNotAdapter(strictEqualsFunc), nArgs: 2 },
167168
deepEqual: { scopeFn: deepEqualsFunc, nArgs: 2 },
168169
deepEquals: { alias: "deepEqual" },
169170
deepStrictEqual: { scopeFn: deepStrictEqualsFunc, nArgs: 2 },
170171
deepStrictEquals: { alias: "deepStrictEqual" },
171172

172-
notEqual: { scopeFn: createExprAdapter("not", equalsFunc), nArgs: 2 },
173+
notEqual: { scopeFn: createNotAdapter(equalsFunc), nArgs: 2 },
173174
notEquals: { alias: "notEqual" },
174-
notDeepEqual: { scopeFn: createExprAdapter("not", deepEqualsFunc), nArgs: 2 },
175+
notDeepEqual: { scopeFn: createNotAdapter(deepEqualsFunc), nArgs: 2 },
175176
notDeepEquals: { alias: "notDeepEqual" },
176177

177178
isTrue: isStrictTrueFunc,
178179
isFalse: isStrictFalseFunc,
179-
isNotTrue: createExprAdapter("not", isStrictTrueFunc),
180-
isNotFalse: createExprAdapter("not", isStrictFalseFunc),
180+
isNotTrue: createNotAdapter(isStrictTrueFunc),
181+
isNotFalse: createNotAdapter(isStrictFalseFunc),
181182

182183
isNull: isNullFunc,
183-
isNotNull: createExprAdapter("not", isNullFunc),
184+
isNotNull: createNotAdapter(isNullFunc),
184185

185186
isUndefined: isUndefinedFunc,
186-
isNotUndefined: createExprAdapter("not", isUndefinedFunc),
187+
isNotUndefined: createNotAdapter(isUndefinedFunc),
187188

188189
isEmpty: isEmptyFunc,
189-
isNotEmpty: createExprAdapter("not", isEmptyFunc),
190+
isNotEmpty: createNotAdapter(isEmptyFunc),
190191

191192
isSealed: isSealedFunc,
192-
isNotSealed: createExprAdapter("not", isSealedFunc),
193+
isNotSealed: createNotAdapter(isSealedFunc),
193194

194195
isFrozen: isFrozenFunc,
195-
isNotFrozen: createExprAdapter("not", isFrozenFunc),
196+
isNotFrozen: createNotAdapter(isFrozenFunc),
196197

197198
isFunction: isFunctionFunc,
198-
isNotFunction: createExprAdapter("not", isFunctionFunc),
199+
isNotFunction: createNotAdapter(isFunctionFunc),
199200

200201
isObject: isObjectFunc,
201-
isNotObject: createExprAdapter("not", isObjectFunc),
202+
isNotObject: createNotAdapter(isObjectFunc),
202203

203204
isPlainObject: isPlainObjectFunc,
204-
isNotPlainObject: createExprAdapter("not", isPlainObjectFunc),
205+
isNotPlainObject: createNotAdapter(isPlainObjectFunc),
205206

206207
includes: { scopeFn: createExprAdapter("includes"), nArgs: 2 }, // The `includes` function is an alias for `hasProperty`
207208

@@ -220,16 +221,16 @@ export function createAssert(): IAssertClass {
220221
isNotBoolean: createExprAdapter("not.is.boolean"),
221222

222223
isExtensible: isExtensibleFunc,
223-
isNotExtensible: createExprAdapter("not", isExtensibleFunc),
224+
isNotExtensible: createNotAdapter(isExtensibleFunc),
224225

225226
isIterable: isIterableFunc,
226-
isNotIterable: createExprAdapter("not", isIterableFunc),
227+
isNotIterable: createNotAdapter(isIterableFunc),
227228

228229
isNaN: createExprAdapter("is.nan"),
229230
isNotNaN: createExprAdapter("not.is.nan"),
230231

231232
typeOf: { scopeFn: typeOfFunc, nArgs: 2 },
232-
notTypeOf: { scopeFn: createExprAdapter("not", typeOfFunc), nArgs: 2 },
233+
notTypeOf: { scopeFn: createNotAdapter(typeOfFunc), nArgs: 2 },
233234

234235

235236
isFinite: createExprAdapter("is.finite"),
@@ -241,32 +242,32 @@ export function createAssert(): IAssertClass {
241242
ifError: { scopeFn: ifErrorFunc, nArgs: 1 },
242243

243244
isInstanceOf: { scopeFn: instanceOfFunc, nArgs: 2 },
244-
isNotInstanceOf: { scopeFn: createExprAdapter("not", instanceOfFunc), nArgs: 2 },
245+
isNotInstanceOf: { scopeFn: createNotAdapter(instanceOfFunc), nArgs: 2 },
245246

246247
throws: { scopeFn: throwsFunc, nArgs: 3 },
247-
doesNotThrow: { scopeFn: createExprAdapter("not", throwsFunc), nArgs: 3 },
248+
doesNotThrow: { scopeFn: createNotAdapter(throwsFunc), nArgs: 3 },
248249

249250
match: { scopeFn: matchFunc, nArgs: 2 },
250-
notMatch: { scopeFn: createExprAdapter("not", matchFunc), nArgs: 2 },
251+
notMatch: { scopeFn: createNotAdapter(matchFunc), nArgs: 2 },
251252

252253
hasProperty: { scopeFn: hasPropertyFunc, nArgs: 3 },
253254
hasOwnProperty: { scopeFn: hasOwnPropertyFunc, nArgs: 3 },
254-
notHasProperty: { scopeFn: createExprAdapter("not", hasPropertyFunc), nArgs: 3 },
255-
notHasOwnProperty: { scopeFn: createExprAdapter("not", hasOwnPropertyFunc), nArgs: 3 },
255+
notHasProperty: { scopeFn: createNotAdapter(hasPropertyFunc), nArgs: 3 },
256+
notHasOwnProperty: { scopeFn: createNotAdapter(hasOwnPropertyFunc), nArgs: 3 },
256257
hasDeepProperty: { scopeFn: hasDeepPropertyFunc, nArgs: 3 },
257-
notHasDeepProperty: { scopeFn: createExprAdapter("not", hasDeepPropertyFunc), nArgs: 3 },
258+
notHasDeepProperty: { scopeFn: createNotAdapter(hasDeepPropertyFunc), nArgs: 3 },
258259
hasDeepOwnProperty: { scopeFn: hasDeepOwnPropertyFunc, nArgs: 3 },
259-
notHasDeepOwnProperty: { scopeFn: createExprAdapter("not", hasDeepOwnPropertyFunc), nArgs: 3 },
260+
notHasDeepOwnProperty: { scopeFn: createNotAdapter(hasDeepOwnPropertyFunc), nArgs: 3 },
260261

261262
// Nested property operations
262263
nestedProperty: { scopeFn: hasNestedPropertyFunc, nArgs: 3 },
263-
notNestedProperty: { scopeFn: createExprAdapter("not", hasNestedPropertyFunc), nArgs: 3 },
264+
notNestedProperty: { scopeFn: createNotAdapter(hasNestedPropertyFunc), nArgs: 3 },
264265
deepNestedProperty: { scopeFn: hasDeepNestedPropertyFunc, nArgs: 3 },
265-
notDeepNestedProperty: { scopeFn: createExprAdapter("not", hasDeepNestedPropertyFunc), nArgs: 3 },
266+
notDeepNestedProperty: { scopeFn: createNotAdapter(hasDeepNestedPropertyFunc), nArgs: 3 },
266267
nestedInclude: { scopeFn: nestedIncludeFunc, nArgs: 2 },
267-
notNestedInclude: { scopeFn: createExprAdapter("not", nestedIncludeFunc), nArgs: 2 },
268+
notNestedInclude: { scopeFn: createNotAdapter(nestedIncludeFunc), nArgs: 2 },
268269
deepNestedInclude: { scopeFn: deepNestedIncludeFunc, nArgs: 2 },
269-
notDeepNestedInclude: { scopeFn: createExprAdapter("not", deepNestedIncludeFunc), nArgs: 2 },
270+
notDeepNestedInclude: { scopeFn: createNotAdapter(deepNestedIncludeFunc), nArgs: 2 },
270271

271272
// Own include operations (checks only own properties, not inherited)
272273
ownInclude: { scopeFn: createExprAdapter("own.include"), nArgs: 2 },
@@ -276,81 +277,81 @@ export function createAssert(): IAssertClass {
276277

277278
// Numeric comparison operations
278279
isAbove: { scopeFn: aboveFunc, nArgs: 2 },
279-
isNotAbove: { scopeFn: createExprAdapter("not", aboveFunc), nArgs: 2 },
280+
isNotAbove: { scopeFn: createNotAdapter(aboveFunc), nArgs: 2 },
280281
isAtLeast: { scopeFn: leastFunc, nArgs: 2 },
281-
isNotAtLeast: { scopeFn: createExprAdapter("not", leastFunc), nArgs: 2 },
282+
isNotAtLeast: { scopeFn: createNotAdapter(leastFunc), nArgs: 2 },
282283
isBelow: { scopeFn: belowFunc, nArgs: 2 },
283-
isNotBelow: { scopeFn: createExprAdapter("not", belowFunc), nArgs: 2 },
284+
isNotBelow: { scopeFn: createNotAdapter(belowFunc), nArgs: 2 },
284285
isAtMost: { scopeFn: mostFunc, nArgs: 2 },
285-
isNotAtMost: { scopeFn: createExprAdapter("not", mostFunc), nArgs: 2 },
286+
isNotAtMost: { scopeFn: createNotAdapter(mostFunc), nArgs: 2 },
286287
isWithin: { scopeFn: withinFunc, nArgs: 3 },
287-
isNotWithin: { scopeFn: createExprAdapter("not", withinFunc), nArgs: 3 },
288+
isNotWithin: { scopeFn: createNotAdapter(withinFunc), nArgs: 3 },
288289

289290
// Length checking
290291
lengthOf: { scopeFn: lengthFunc, nArgs: 2 },
291-
notLengthOf: { scopeFn: createExprAdapter("not", lengthFunc), nArgs: 2 },
292+
notLengthOf: { scopeFn: createNotAdapter(lengthFunc), nArgs: 2 },
292293
sizeOf: { alias: "lengthOf" },
293294
notSizeOf: { alias: "notLengthOf" },
294295

295296
// Approximate equality (closeTo)
296297
closeTo: { scopeFn: closeToFunc, nArgs: 3 },
297-
notCloseTo: { scopeFn: createExprAdapter("not", closeToFunc), nArgs: 3 },
298+
notCloseTo: { scopeFn: createNotAdapter(closeToFunc), nArgs: 3 },
298299
approximately: { alias: "closeTo" },
299300
notApproximately: { alias: "notCloseTo" },
300301

301302
// Change/increase/decrease detection
302303
changes: { scopeFn: changesFunc, nArgs: 3 },
303-
doesNotChange: { scopeFn: createExprAdapter("not", changesFunc), nArgs: 3 },
304+
doesNotChange: { scopeFn: createNotAdapter(changesFunc), nArgs: 3 },
304305
changesBy: { scopeFn: changesByFunc, nArgs: 4 },
305-
notChangesBy: { scopeFn: createExprAdapter("not", changesByFunc), nArgs: 4 },
306+
notChangesBy: { scopeFn: createNotAdapter(changesByFunc), nArgs: 4 },
306307
changesButNotBy: { scopeFn: changesButNotByFunc, nArgs: 4 },
307308
increases: { scopeFn: increasesFunc, nArgs: 3 },
308-
doesNotIncrease: { scopeFn: createExprAdapter("not", increasesFunc), nArgs: 3 },
309+
doesNotIncrease: { scopeFn: createNotAdapter(increasesFunc), nArgs: 3 },
309310
increasesBy: { scopeFn: increasesByFunc, nArgs: 4 },
310-
notIncreasesBy: { scopeFn: createExprAdapter("not", increasesByFunc), nArgs: 4 },
311+
notIncreasesBy: { scopeFn: createNotAdapter(increasesByFunc), nArgs: 4 },
311312
increasesButNotBy: { scopeFn: increasesButNotByFunc, nArgs: 4 },
312313
decreases: { scopeFn: decreasesFunc, nArgs: 3 },
313-
doesNotDecrease: { scopeFn: createExprAdapter("not", decreasesFunc), nArgs: 3 },
314+
doesNotDecrease: { scopeFn: createNotAdapter(decreasesFunc), nArgs: 3 },
314315
decreasesBy: { scopeFn: decreasesByFunc, nArgs: 4 },
315-
notDecreasesBy: { scopeFn: createExprAdapter("not", decreasesByFunc), nArgs: 4 },
316+
notDecreasesBy: { scopeFn: createNotAdapter(decreasesByFunc), nArgs: 4 },
316317
decreasesButNotBy: { scopeFn: decreasesButNotByFunc, nArgs: 4 },
317318

318319
// Value membership (oneOf)
319320
oneOf: { scopeFn: oneOfFunc, nArgs: 2 },
320-
notOneOf: { scopeFn: createExprAdapter("not", oneOfFunc), nArgs: 2 },
321+
notOneOf: { scopeFn: createNotAdapter(oneOfFunc), nArgs: 2 },
321322

322323
// Operator comparison
323324
operator: { scopeFn: operatorFunc, nArgs: 3 },
324325

325326
// Member comparison
326327
sameMembers: { scopeFn: sameMembersFunc, nArgs: 2 },
327-
notSameMembers: { scopeFn: createExprAdapter("not", sameMembersFunc), nArgs: 2 },
328+
notSameMembers: { scopeFn: createNotAdapter(sameMembersFunc), nArgs: 2 },
328329
sameDeepMembers: { scopeFn: sameDeepMembersFunc, nArgs: 2 },
329-
notSameDeepMembers: { scopeFn: createExprAdapter("not", sameDeepMembersFunc), nArgs: 2 },
330+
notSameDeepMembers: { scopeFn: createNotAdapter(sameDeepMembersFunc), nArgs: 2 },
330331
sameOrderedMembers: { scopeFn: sameOrderedMembersFunc, nArgs: 2 },
331-
notSameOrderedMembers: { scopeFn: createExprAdapter("not", sameOrderedMembersFunc), nArgs: 2 },
332+
notSameOrderedMembers: { scopeFn: createNotAdapter(sameOrderedMembersFunc), nArgs: 2 },
332333
sameDeepOrderedMembers: { scopeFn: sameDeepOrderedMembersFunc, nArgs: 2 },
333-
notSameDeepOrderedMembers: { scopeFn: createExprAdapter("not", sameDeepOrderedMembersFunc), nArgs: 2 },
334+
notSameDeepOrderedMembers: { scopeFn: createNotAdapter(sameDeepOrderedMembersFunc), nArgs: 2 },
334335
includeMembers: { scopeFn: includeMembersFunc, nArgs: 2 },
335-
notIncludeMembers: { scopeFn: createExprAdapter("not", includeMembersFunc), nArgs: 2 },
336+
notIncludeMembers: { scopeFn: createNotAdapter(includeMembersFunc), nArgs: 2 },
336337
includeDeepMembers: { scopeFn: includeDeepMembersFunc, nArgs: 2 },
337-
notIncludeDeepMembers: { scopeFn: createExprAdapter("not", includeDeepMembersFunc), nArgs: 2 },
338+
notIncludeDeepMembers: { scopeFn: createNotAdapter(includeDeepMembersFunc), nArgs: 2 },
338339
includeOrderedMembers: { scopeFn: includeOrderedMembersFunc, nArgs: 2 },
339-
notIncludeOrderedMembers: { scopeFn: createExprAdapter("not", includeOrderedMembersFunc), nArgs: 2 },
340+
notIncludeOrderedMembers: { scopeFn: createNotAdapter(includeOrderedMembersFunc), nArgs: 2 },
340341
includeDeepOrderedMembers: { scopeFn: includeDeepOrderedMembersFunc, nArgs: 2 },
341-
notIncludeDeepOrderedMembers: { scopeFn: createExprAdapter("not", includeDeepOrderedMembersFunc), nArgs: 2 },
342+
notIncludeDeepOrderedMembers: { scopeFn: createNotAdapter(includeDeepOrderedMembersFunc), nArgs: 2 },
342343
startsWithMembers: { scopeFn: startsWithMembersFunc, nArgs: 2 },
343-
notStartsWithMembers: { scopeFn: createExprAdapter("not", startsWithMembersFunc), nArgs: 2 },
344+
notStartsWithMembers: { scopeFn: createNotAdapter(startsWithMembersFunc), nArgs: 2 },
344345
startsWithDeepMembers: { scopeFn: startsWithDeepMembersFunc, nArgs: 2 },
345-
notStartsWithDeepMembers: { scopeFn: createExprAdapter("not", startsWithDeepMembersFunc), nArgs: 2 },
346+
notStartsWithDeepMembers: { scopeFn: createNotAdapter(startsWithDeepMembersFunc), nArgs: 2 },
346347
endsWithMembers: { scopeFn: endsWithMembersFunc, nArgs: 2 },
347-
notEndsWithMembers: { scopeFn: createExprAdapter("not", endsWithMembersFunc), nArgs: 2 },
348+
notEndsWithMembers: { scopeFn: createNotAdapter(endsWithMembersFunc), nArgs: 2 },
348349
endsWithDeepMembers: { scopeFn: endsWithDeepMembersFunc, nArgs: 2 },
349-
notEndsWithDeepMembers: { scopeFn: createExprAdapter("not", endsWithDeepMembersFunc), nArgs: 2 },
350+
notEndsWithDeepMembers: { scopeFn: createNotAdapter(endsWithDeepMembersFunc), nArgs: 2 },
350351
subsequence: { scopeFn: subsequenceFunc, nArgs: 2 },
351-
notSubsequence: { scopeFn: createExprAdapter("not", subsequenceFunc), nArgs: 2 },
352+
notSubsequence: { scopeFn: createNotAdapter(subsequenceFunc), nArgs: 2 },
352353
deepSubsequence: { scopeFn: deepSubsequenceFunc, nArgs: 2 },
353-
notDeepSubsequence: { scopeFn: createExprAdapter("not", deepSubsequenceFunc), nArgs: 2 },
354+
notDeepSubsequence: { scopeFn: createNotAdapter(deepSubsequenceFunc), nArgs: 2 },
354355

355356
// Keys operations (non-deep, uses strict equality)
356357
hasAnyKeys: { scopeFn: createExprAdapter("has.any.keys"), nArgs: 2 },

core/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import { IFormatCtx, IFormatter, IFormattedValue, IFormatterOptions, eFormatResu
5757
import { createEvalAdapter, EvalFn } from "./assert/adapters/evalAdapter";
5858
import { IPropertyResultOp } from "./assert/interface/ops/IPropertyResultOp";
5959
import { createExprAdapter } from "./assert/adapters/exprAdapter";
60+
import { createNotAdapter } from "./assert/adapters/notAdapter";
6061
import { createAssertScope } from "./assert/assertScope";
6162
import { AssertInstHandlers, IAssertInstHandlers } from "./assert/interface/IAssertInstHandlers";
6263
import { CHECK_INTERNAL_STACK_FRAME_REGEX } from "./assert/const";
@@ -113,7 +114,7 @@ export {
113114
export {
114115
addAssertFunc, addAssertFuncs, addAssertInstFunc, addAssertInstFuncDef, addAssertInstFuncDefs,
115116
assert, assertConfig, createAssert, createAssertScope, createContext, createEvalAdapter,
116-
createExprAdapter, expect, getScopeContext, useScope
117+
createExprAdapter, createNotAdapter, expect, getScopeContext, useScope
117118
};
118119

119120
/**

0 commit comments

Comments
 (0)