From d31447306b2fec27ec2c6011bc8d5179537fee36 Mon Sep 17 00:00:00 2001 From: Danilo Alonso Date: Fri, 26 Jun 2026 14:05:53 -0400 Subject: [PATCH 1/7] refactor: convert to native ESM with vitest + oxc toolchain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert @hapi/formula from CommonJS to native ESM and replace the @hapi/lab + @hapi/code + @hapi/eslint-plugin toolchain with vitest + @vitest/coverage-v8 and oxlint + oxfmt (via @hapi/oxc-plugin). - "type": "module"; single "." export with a co-located lib/index.d.ts; ship API.md + README.md in the published package - drop the internals namespace (module scope is already private) - engines.node >= 22; CI on the min-node-22-hapi-21 reusable workflow ESM-only — CommonJS require('@hapi/formula') no longer resolves. --- .eslintignore | 1 - .github/workflows/ci-module.yml | 14 +- .gitignore | 2 +- API.md | 13 +- lib/index.d.ts | 40 +- lib/index.js | 304 ++++++------- oxfmt.config.ts | 8 + oxlint.config.ts | 11 + package.json | 47 +- test/index.js | 732 +++++++++++++++----------------- test/index.ts | 35 -- test/typings.ts | 52 +++ tsconfig.json | 25 ++ vitest.config.ts | 23 + 14 files changed, 646 insertions(+), 661 deletions(-) delete mode 100644 .eslintignore create mode 100644 oxfmt.config.ts create mode 100644 oxlint.config.ts delete mode 100755 test/index.ts create mode 100755 test/typings.ts create mode 100644 tsconfig.json create mode 100644 vitest.config.ts diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 1521c8b..0000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/.github/workflows/ci-module.yml b/.github/workflows/ci-module.yml index 9dc4315..4b7ee12 100644 --- a/.github/workflows/ci-module.yml +++ b/.github/workflows/ci-module.yml @@ -1,12 +1,12 @@ name: ci on: - push: - branches: - - master - pull_request: - workflow_dispatch: + push: + branches: + - master + pull_request: + workflow_dispatch: jobs: - test: - uses: hapijs/.github/.github/workflows/ci-module.yml@master + test: + uses: hapijs/.github/.github/workflows/ci-module.yml@min-node-22-hapi-21 diff --git a/.gitignore b/.gitignore index 8f679c9..af5a347 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ **/node_modules **/package-lock.json -coverage.* +coverage/ **/.DS_Store **/._* diff --git a/API.md b/API.md index fc99777..ba4f601 100755 --- a/API.md +++ b/API.md @@ -3,25 +3,22 @@ ```js const Formula = require('@hapi/formula'); - const functions = { - x: (value) => value + 10 + x: (value) => value + 10, }; const constants = { - Z: 100 + Z: 100, }; const reference = function (name) { - return (context) => context[name]; }; - const formula = new Formula.Parser('1 + a.b.c.2.4.x + [b] + x([y + 4] + Z)', { functions, constants, reference }); -formula.evaluate({ 'a.b.c.2.4.x': 2, b: 3, 'y + 4': 5 }); // 1 + 2 + 3 + 5 + 10 + 100 -formula.evaluate({ 'a.b.c.2.4.x': '2', b: 3, 'y + 4': '5' }); // '123510010' +formula.evaluate({ 'a.b.c.2.4.x': 2, b: 3, 'y + 4': 5 }); // 1 + 2 + 3 + 5 + 10 + 100 +formula.evaluate({ 'a.b.c.2.4.x': '2', b: 3, 'y + 4': '5' }); // '123510010' ``` ## Methods @@ -29,6 +26,7 @@ formula.evaluate({ 'a.b.c.2.4.x': '2', b: 3, 'y + 4': '5' }); // '123510010' ### `new Formula.Parser(formula, [options])` Creates a new formula parser object where: + - `formula` - the formula string to parse. - `options` - optional settings: - `constants` - a hash of key-value pairs used to convert constants to values. @@ -39,6 +37,7 @@ Creates a new formula parser object where: ### `parser.evaluate([context])` Evaluate the formula where: + - `context` - optional object with runtime formula context used to resolve variables. Returns the string or number outcome of the resolved formula. diff --git a/lib/index.d.ts b/lib/index.d.ts index d78cc1d..f5a0f48 100755 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1,52 +1,36 @@ -/** - * Formula parser - */ -export class Parser { - +/** Formula parser */ +export class Parser { /** * Create a new formula parser. - * - * @param formula - the formula string to parse. - * @param options - optional settings. + * + * @param formula - The formula string to parse. + * @param options - Optional settings. */ constructor(formula: string, options?: Options); /** * Evaluate the formula. - * - * @param context - optional object with runtime formula context used to resolve variables. - * - * @returns the string or number outcome of the resolved formula. + * + * @param context - Optional object with runtime formula context used to resolve variables. + * @returns The string or number outcome of the resolved formula. */ evaluate(context?: any): T; } - export interface Options { - - /** - * A hash of key - value pairs used to convert constants to values. - */ + /** A hash of key - value pairs used to convert constants to values. */ readonly constants?: Record; - /** - * A regular expression used to validate token variables. - */ + /** A regular expression used to validate token variables. */ readonly tokenRx?: RegExp; - /** - * A variable resolver factory function. - */ + /** A variable resolver factory function. */ readonly reference?: Options.Reference; - /** - * A hash of key-value pairs used to resolve formula functions. - */ + /** A hash of key-value pairs used to resolve formula functions. */ readonly functions?: Record; } - export namespace Options { - type Reference = (name: string) => (context: any) => any; } diff --git a/lib/index.js b/lib/index.js index 4b4d0a2..504660a 100755 --- a/lib/index.js +++ b/lib/index.js @@ -1,44 +1,35 @@ -'use strict'; - -const internals = { - operators: ['!', '^', '*', '/', '%', '+', '-', '<', '<=', '>', '>=', '==', '!=', '&&', '||', '??'], - operatorCharacters: ['!', '^', '*', '/', '%', '+', '-', '<', '=', '>', '&', '|', '?'], - operatorsOrder: [['^'], ['*', '/', '%'], ['+', '-'], ['<', '<=', '>', '>='], ['==', '!='], ['&&'], ['||', '??']], - operatorsPrefix: ['!', 'n'], - - literals: { - '"': '"', - '`': '`', - '\'': '\'', - '[': ']' - }, - - numberRx: /^(?:[0-9]*(\.[0-9]*)?){1}$/, - tokenRx: /^[\w\$\#\.\@\:\{\}]+$/, - - symbol: Symbol('formula'), - settings: Symbol('settings') +const operators = ['!', '^', '*', '/', '%', '+', '-', '<', '<=', '>', '>=', '==', '!=', '&&', '||', '??']; +const operatorCharacters = ['!', '^', '*', '/', '%', '+', '-', '<', '=', '>', '&', '|', '?']; +const operatorsOrder = [['^'], ['*', '/', '%'], ['+', '-'], ['<', '<=', '>', '>='], ['==', '!='], ['&&'], ['||', '??']]; +const operatorsPrefix = ['!', 'n']; + +const literals = { + '"': '"', + '`': '`', + "'": "'", + '[': ']', }; +const numberRx = /^(?:[0-9]*(\.[0-9]*)?){1}$/; +const tokenRx = /^[\w\$\#\.\@\:\{\}]+$/; -exports.Parser = class { +const symbol = Symbol('formula'); +const settingsSymbol = Symbol('settings'); +export class Parser { constructor(string, options = {}) { - - if (!options[internals.settings] && - options.constants) { - + if (!options[settingsSymbol] && options.constants) { for (const constant in options.constants) { const value = options.constants[constant]; - if (value !== null && - !['boolean', 'number', 'string'].includes(typeof value)) { - + if (value !== null && !['boolean', 'number', 'string'].includes(typeof value)) { throw new Error(`Formula constant ${constant} contains invalid ${typeof value} value type`); } } } - this.settings = options[internals.settings] ? options : Object.assign({ [internals.settings]: true, constants: {}, functions: {} }, options); + this.settings = options[settingsSymbol] + ? options + : Object.assign({ [settingsSymbol]: true, constants: {}, functions: {} }, options); this.single = null; this._parts = null; @@ -46,30 +37,24 @@ exports.Parser = class { } _parse(string) { - let parts = []; let current = ''; let parenthesis = 0; let literal = false; const flush = (inner) => { - if (parenthesis) { throw new Error('Formula missing closing parenthesis'); } const last = parts.length ? parts[parts.length - 1] : null; - if (!literal && - !current && - !inner) { - + if (!literal && !current && !inner) { return; } - if (last && - last.type === 'reference' && - inner === ')') { // Function + if (last && last.type === 'reference' && inner === ')') { + // Function last.type = 'function'; last.value = this._subFormula(current, last.value); @@ -77,38 +62,37 @@ exports.Parser = class { return; } - if (inner === ')') { // Segment - const sub = new exports.Parser(current, this.settings); + if (inner === ')') { + // Segment + const sub = new Parser(current, this.settings); parts.push({ type: 'segment', value: sub }); - } - else if (literal) { - if (literal === ']') { // Reference + } else if (literal) { + if (literal === ']') { + // Reference parts.push({ type: 'reference', value: current }); current = ''; return; } - parts.push({ type: 'literal', value: current }); // Literal - } - else if (internals.operatorCharacters.includes(current)) { // Operator - if (last && - last.type === 'operator' && - internals.operators.includes(last.value + current)) { // 2 characters operator + parts.push({ type: 'literal', value: current }); // Literal + } else if (operatorCharacters.includes(current)) { + // Operator + if (last && last.type === 'operator' && operators.includes(last.value + current)) { + // 2 characters operator last.value += current; - } - else { + } else { parts.push({ type: 'operator', value: current }); } - } - else if (current.match(internals.numberRx)) { // Number + } else if (current.match(numberRx)) { + // Number parts.push({ type: 'constant', value: parseFloat(current) }); - } - else if (this.settings.constants[current] !== undefined) { // Constant + } else if (this.settings.constants[current] !== undefined) { + // Constant parts.push({ type: 'constant', value: this.settings.constants[current] }); - } - else { // Reference - if (!current.match(internals.tokenRx)) { + } else { + // Reference + if (!current.match(tokenRx)) { throw new Error(`Formula contains invalid token: ${current}`); } @@ -123,45 +107,35 @@ exports.Parser = class { if (c === literal) { flush(); literal = false; - } - else { + } else { current += c; } - } - else if (parenthesis) { + } else if (parenthesis) { if (c === '(') { current += c; ++parenthesis; - } - else if (c === ')') { + } else if (c === ')') { --parenthesis; if (!parenthesis) { flush(c); - } - else { + } else { current += c; } - } - else { + } else { current += c; } - } - else if (c in internals.literals) { - literal = internals.literals[c]; - } - else if (c === '(') { + } else if (c in literals) { + literal = literals[c]; + } else if (c === '(') { flush(); ++parenthesis; - } - else if (internals.operatorCharacters.includes(c)) { + } else if (operatorCharacters.includes(c)) { flush(); current = c; flush(); - } - else if (c !== ' ') { + } else if (c !== ' ') { current += c; - } - else { + } else { flush(); } } @@ -171,11 +145,7 @@ exports.Parser = class { // Replace prefix - to internal negative operator parts = parts.map((part, i) => { - - if (part.type !== 'operator' || - part.value !== '-' || - i && parts[i - 1].type !== 'operator') { - + if (part.type !== 'operator' || part.value !== '-' || (i && parts[i - 1].type !== 'operator')) { return part; } @@ -187,7 +157,7 @@ exports.Parser = class { let operator = false; for (const part of parts) { if (part.type === 'operator') { - if (internals.operatorsPrefix.includes(part.value)) { + if (operatorsPrefix.includes(part.value)) { continue; } @@ -195,11 +165,10 @@ exports.Parser = class { throw new Error('Formula contains an operator in invalid position'); } - if (!internals.operators.includes(part.value)) { + if (!operators.includes(part.value)) { throw new Error(`Formula contains an unknown operator ${part.value}`); } - } - else if (operator) { + } else if (operator) { throw new Error('Formula missing expected operator'); } @@ -212,20 +181,17 @@ exports.Parser = class { // Identify single part - if (parts.length === 1 && - ['reference', 'literal', 'constant'].includes(parts[0].type)) { - + if (parts.length === 1 && ['reference', 'literal', 'constant'].includes(parts[0].type)) { this.single = { type: parts[0].type === 'reference' ? 'reference' : 'value', value: parts[0].value }; } // Process parts this._parts = parts.map((part) => { - // Operators if (part.type === 'operator') { - return internals.operatorsPrefix.includes(part.value) ? part : part.value; + return operatorsPrefix.includes(part.value) ? part : part.value; } // Literals, constants, segments @@ -236,9 +202,7 @@ exports.Parser = class { // References - if (this.settings.tokenRx && - !this.settings.tokenRx.test(part.value)) { - + if (this.settings.tokenRx && !this.settings.tokenRx.test(part.value)) { throw new Error(`Formula contains invalid reference ${part.value}`); } @@ -246,12 +210,11 @@ exports.Parser = class { return this.settings.reference(part.value); } - return internals.reference(part.value); + return reference(part.value); }); } _subFormula(string, name) { - const method = this.settings.functions[name]; if (typeof method !== 'function') { throw new Error(`Formula contains unknown function ${name}`); @@ -264,7 +227,6 @@ exports.Parser = class { let literal = false; const flush = () => { - if (!current) { throw new Error(`Formula contains function ${name} with invalid arguments ${string}`); } @@ -280,24 +242,16 @@ exports.Parser = class { if (c === literal) { literal = false; } - } - else if (c in internals.literals && - !parenthesis) { - + } else if (c in literals && !parenthesis) { current += c; - literal = internals.literals[c]; - } - else if (c === ',' && - !parenthesis) { - + literal = literals[c]; + } else if (c === ',' && !parenthesis) { flush(); - } - else { + } else { current += c; if (c === '(') { ++parenthesis; - } - else if (c === ')') { + } else if (c === ')') { --parenthesis; } } @@ -306,10 +260,9 @@ exports.Parser = class { flush(); } - args = args.map((arg) => new exports.Parser(arg, this.settings)); + args = args.map((arg) => new Parser(arg, this.settings)); return function (context) { - const innerValues = []; for (const arg of args) { innerValues.push(arg.evaluate(context)); @@ -320,62 +273,51 @@ exports.Parser = class { } evaluate(context) { - const parts = this._parts.slice(); // Prefix operators for (let i = parts.length - 2; i >= 0; --i) { const part = parts[i]; - if (part && - part.type === 'operator') { - + if (part && part.type === 'operator') { const current = parts[i + 1]; parts.splice(i + 1, 1); - const value = internals.evaluate(current, context); - parts[i] = internals.single(part.value, value); + const value = evaluate(current, context); + parts[i] = single(part.value, value); } } // Left-right operators - internals.operatorsOrder.forEach((set) => { - - for (let i = 1; i < parts.length - 1;) { + operatorsOrder.forEach((set) => { + for (let i = 1; i < parts.length - 1; ) { if (set.includes(parts[i])) { const operator = parts[i]; - const left = internals.evaluate(parts[i - 1], context); - const right = internals.evaluate(parts[i + 1], context); + const left = evaluate(parts[i - 1], context); + const right = evaluate(parts[i + 1], context); parts.splice(i, 2); - const result = internals.calculate(operator, left, right); - parts[i - 1] = result === 0 ? 0 : result; // Convert -0 - } - else { + const result = calculate(operator, left, right); + parts[i - 1] = result === 0 ? 0 : result; // Convert -0 + } else { i += 2; } } }); - return internals.evaluate(parts[0], context); + return evaluate(parts[0], context); } -}; - +} -exports.Parser.prototype[internals.symbol] = true; - - -internals.reference = function (name) { +Parser.prototype[symbol] = true; +function reference(name) { return function (context) { - return context && context[name] !== undefined ? context[name] : null; }; -}; - - -internals.evaluate = function (part, context) { +} +function evaluate(part, context) { if (part === null) { return null; } @@ -384,16 +326,14 @@ internals.evaluate = function (part, context) { return part(context); } - if (part[internals.symbol]) { + if (part[symbol]) { return part.evaluate(context); } return part; -}; - - -internals.single = function (operator, value) { +} +function single(operator, value) { if (operator === '!') { return value ? false : true; } @@ -401,56 +341,64 @@ internals.single = function (operator, value) { // operator === 'n' const negative = -value; - if (negative === 0) { // Override -0 + if (negative === 0) { + // Override -0 return 0; } return negative; -}; - - -internals.calculate = function (operator, left, right) { +} +function calculate(operator, left, right) { if (operator === '??') { - return internals.exists(left) ? left : right; + return exists(left) ? left : right; } - if (typeof left === 'string' || - typeof right === 'string') { - + if (typeof left === 'string' || typeof right === 'string') { if (operator === '+') { - left = internals.exists(left) ? left : ''; - right = internals.exists(right) ? right : ''; + left = exists(left) ? left : ''; + right = exists(right) ? right : ''; return left + right; } - } - else { + } else { switch (operator) { - case '^': return Math.pow(left, right); - case '*': return left * right; - case '/': return left / right; - case '%': return left % right; - case '+': return left + right; - case '-': return left - right; + case '^': + return Math.pow(left, right); + case '*': + return left * right; + case '/': + return left / right; + case '%': + return left % right; + case '+': + return left + right; + case '-': + return left - right; } } switch (operator) { - case '<': return left < right; - case '<=': return left <= right; - case '>': return left > right; - case '>=': return left >= right; - case '==': return left === right; - case '!=': return left !== right; - case '&&': return left && right; - case '||': return left || right; + case '<': + return left < right; + case '<=': + return left <= right; + case '>': + return left > right; + case '>=': + return left >= right; + case '==': + return left === right; + case '!=': + return left !== right; + case '&&': + return left && right; + case '||': + return left || right; } return null; -}; - - -internals.exists = function (value) { +} +function exists(value) { return value !== null && value !== undefined; -}; +} diff --git a/oxfmt.config.ts b/oxfmt.config.ts new file mode 100644 index 0000000..12e357b --- /dev/null +++ b/oxfmt.config.ts @@ -0,0 +1,8 @@ +import DefaultOxfmtConfig from '@hapi/oxc-plugin/oxfmt'; +import { defineConfig } from 'oxfmt'; + +import type { OxfmtConfig } from 'oxfmt'; + +export default defineConfig({ + ...DefaultOxfmtConfig, +}) as OxfmtConfig; diff --git a/oxlint.config.ts b/oxlint.config.ts new file mode 100644 index 0000000..fd4548e --- /dev/null +++ b/oxlint.config.ts @@ -0,0 +1,11 @@ +import HapiRecommended from '@hapi/oxc-plugin/oxlint'; +import { defineConfig } from 'oxlint'; + +import type { OxlintConfig } from 'oxlint'; + +export default defineConfig({ + extends: [HapiRecommended], + env: { + ...HapiRecommended.env, + }, +}) as OxlintConfig; diff --git a/package.json b/package.json index 74b3775..3f5badd 100755 --- a/package.json +++ b/package.json @@ -1,29 +1,46 @@ { "name": "@hapi/formula", - "description": "Math and string formula parser.", "version": "3.0.2", - "repository": "git://github.com/hapijs/formula", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "description": "Math and string formula parser.", "keywords": [ "formula", - "parser", "math", + "parser", "string" ], + "license": "BSD-3-Clause", + "repository": "git://github.com/hapijs/formula", "files": [ - "lib" + "lib", + "API.md", + "README.md" ], - "dependencies": {}, - "devDependencies": { - "@hapi/code": "^9.0.3", - "@hapi/lab": "^25.1.2", - "@types/node": "^14.18.36", - "typescript": "4.0.x" + "type": "module", + "types": "lib/index.d.ts", + "exports": { + ".": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + } }, "scripts": { - "test": "lab -a @hapi/code -t 100 -L -Y", - "test-cov-html": "lab -a @hapi/code -t 100 -L -r html -o coverage.html" + "test": "vitest run --coverage", + "typecheck": "tsc --noEmit", + "lint": "oxlint", + "lint:fix": "oxlint --fix", + "fmt": "oxfmt --check", + "fmt:fix": "oxfmt", + "check": "npm run lint && npm run fmt && npm run typecheck && npm test" + }, + "devDependencies": { + "@hapi/oxc-plugin": "^1.0.1", + "@vitest/coverage-v8": "^4.1.9", + "oxfmt": "^0.55.0", + "oxlint": "^1.70.0", + "typescript": "^6.0.3", + "vitest": "^4.1.9" }, - "license": "BSD-3-Clause" + "engines": { + "node": ">=22" + } } diff --git a/test/index.js b/test/index.js index 897db8a..4dbd186 100755 --- a/test/index.js +++ b/test/index.js @@ -1,650 +1,604 @@ -'use strict'; - -const Code = require('@hapi/code'); -const Formula = require('..'); -const Lab = require('@hapi/lab'); - - -const internals = {}; - - -const { describe, it } = exports.lab = Lab.script(); -const expect = Code.expect; +import { describe, expect, it } from 'vitest'; +import * as Formula from '../lib/index.js'; describe('Formula', () => { - it('evaluates a formula', () => { - const functions = { - x: (value) => value + 10 + x: (value) => value + 10, }; const constants = { - Z: 100 + Z: 100, }; const formula = new Formula.Parser('1 + a.b.c.2.4.x + [b] + x([y + 4] + Z)', { functions, constants }); - expect(formula.evaluate({ 'a.b.c.2.4.x': 2, b: 3, 'y + 4': 5 })).to.equal(1 + 2 + 3 + 5 + 10 + 100); - expect(formula.evaluate({ 'a.b.c.2.4.x': '2', b: 3, 'y + 4': '5' })).to.equal('123510010'); + expect(formula.evaluate({ 'a.b.c.2.4.x': 2, b: 3, 'y + 4': 5 })).toEqual(1 + 2 + 3 + 5 + 10 + 100); + expect(formula.evaluate({ 'a.b.c.2.4.x': '2', b: 3, 'y + 4': '5' })).toEqual('123510010'); }); it('evaluates a formula (custom reference handler)', () => { - const functions = { - x: (value) => value + 10 + x: (value) => value + 10, }; const constants = { - Z: 100 + Z: 100, }; const reference = function (name) { - return (context) => context[name]; }; - const formula = new Formula.Parser('1 + a.b.c.2.4.x + [b] + x([y + 4] + Z)', { functions, constants, reference }); - expect(formula.evaluate({ 'a.b.c.2.4.x': 2, b: 3, 'y + 4': 5 })).to.equal(1 + 2 + 3 + 5 + 10 + 100); - expect(formula.evaluate({ 'a.b.c.2.4.x': '2', b: 3, 'y + 4': '5' })).to.equal('123510010'); + const formula = new Formula.Parser('1 + a.b.c.2.4.x + [b] + x([y + 4] + Z)', { + functions, + constants, + reference, + }); + expect(formula.evaluate({ 'a.b.c.2.4.x': 2, b: 3, 'y + 4': 5 })).toEqual(1 + 2 + 3 + 5 + 10 + 100); + expect(formula.evaluate({ 'a.b.c.2.4.x': '2', b: 3, 'y + 4': '5' })).toEqual('123510010'); }); describe('constructor()', () => { - it('allows valid constant', () => { - - expect(() => new Formula.Parser('1 + x', { constants: { x: true } })).to.not.throw(); - expect(() => new Formula.Parser('1 + x', { constants: { x: 1 } })).to.not.throw(); - expect(() => new Formula.Parser('1 + x', { constants: { x: 'x' } })).to.not.throw(); - expect(() => new Formula.Parser('1 + x', { constants: { x: null } })).to.not.throw(); + expect(() => new Formula.Parser('1 + x', { constants: { x: true } })).not.toThrow(); + expect(() => new Formula.Parser('1 + x', { constants: { x: 1 } })).not.toThrow(); + expect(() => new Formula.Parser('1 + x', { constants: { x: 'x' } })).not.toThrow(); + expect(() => new Formula.Parser('1 + x', { constants: { x: null } })).not.toThrow(); }); it('errors on invalid constant', () => { - - expect(() => new Formula.Parser('1 + x', { constants: { x: {} } })).to.throw('Formula constant x contains invalid object value type'); - expect(() => new Formula.Parser('1 + x', { constants: { x: () => null } })).to.throw('Formula constant x contains invalid function value type'); - expect(() => new Formula.Parser('1 + x', { constants: { x: undefined } })).to.throw('Formula constant x contains invalid undefined value type'); + expect(() => new Formula.Parser('1 + x', { constants: { x: {} } })).toThrow( + 'Formula constant x contains invalid object value type', + ); + expect(() => new Formula.Parser('1 + x', { constants: { x: () => null } })).toThrow( + 'Formula constant x contains invalid function value type', + ); + expect(() => new Formula.Parser('1 + x', { constants: { x: undefined } })).toThrow( + 'Formula constant x contains invalid undefined value type', + ); }); }); describe('single', () => { - it('identifies single literal (string)', () => { - const formula = new Formula.Parser('"x"'); - expect(formula.single).to.equal({ type: 'value', value: 'x' }); + expect(formula.single).toEqual({ type: 'value', value: 'x' }); }); it('identifies single literal (number)', () => { - const formula = new Formula.Parser('123'); - expect(formula.single).to.equal({ type: 'value', value: 123 }); + expect(formula.single).toEqual({ type: 'value', value: 123 }); }); it('identifies single literal (constant)', () => { - const formula = new Formula.Parser('x', { constants: { x: 'y' } }); - expect(formula.single).to.equal({ type: 'value', value: 'y' }); + expect(formula.single).toEqual({ type: 'value', value: 'y' }); }); it('identifies single reference', () => { - const formula = new Formula.Parser('x'); - expect(formula.single).to.equal({ type: 'reference', value: 'x' }); + expect(formula.single).toEqual({ type: 'reference', value: 'x' }); }); it('identifies non-single reference', () => { - const formula = new Formula.Parser('-x'); - expect(formula.single).to.be.null(); + expect(formula.single).toBeNull(); }); }); describe('_parse()', () => { - it('parses multiple nested functions', () => { - const functions = { x: (value) => value + 1, y: (value) => value + 2, z: (value) => value + 3, - a: () => 9 + a: () => 9, }; const formula = new Formula.Parser('x(10) + y(z(30)) + z(x(40)) + a()', { functions }); - expect(formula.evaluate()).to.equal(11 + 33 + 2 + 41 + 3 + 9); + expect(formula.evaluate()).toEqual(11 + 33 + 2 + 41 + 3 + 9); }); it('passes context as this to functions', () => { - const functions = { x: function () { - return this.X; - } + }, }; const formula = new Formula.Parser('x()', { functions }); - expect(formula.evaluate({ X: 1 })).to.equal(1); + expect(formula.evaluate({ X: 1 })).toEqual(1); }); it('parses parenthesis', () => { - const formula = new Formula.Parser('(x + 4) * (x - 5) / (x ^ 2)'); - expect(formula.evaluate({ x: 10 })).to.equal((10 + 4) * (10 - 5) / (Math.pow(10, 2))); + expect(formula.evaluate({ x: 10 })).toEqual(((10 + 4) * (10 - 5)) / Math.pow(10, 2)); }); it('parses string literals', () => { - const formula = new Formula.Parser('"x+3" + `y()` + \'-z\''); - expect(formula.evaluate()).to.equal('x+3y()-z'); + expect(formula.evaluate()).toEqual('x+3y()-z'); }); it('validates token', () => { - - expect(() => new Formula.Parser('[xy]', { tokenRx: /^\w+$/ })).to.not.throw(); - expect(() => new Formula.Parser('[x y]', { tokenRx: /^\w+$/ })).to.throw('Formula contains invalid reference x y'); + expect(() => new Formula.Parser('[xy]', { tokenRx: /^\w+$/ })).not.toThrow(); + expect(() => new Formula.Parser('[x y]', { tokenRx: /^\w+$/ })).toThrow( + 'Formula contains invalid reference x y', + ); }); it('errors on missing closing parenthesis', () => { - - expect(() => new Formula.Parser('(a + 2(')).to.throw('Formula missing closing parenthesis'); + expect(() => new Formula.Parser('(a + 2(')).toThrow('Formula missing closing parenthesis'); }); it('errors on invalid character', () => { - - expect(() => new Formula.Parser('x\u0000')).to.throw('Formula contains invalid token: x\u0000'); + expect(() => new Formula.Parser('x\u0000')).toThrow('Formula contains invalid token: x\u0000'); }); it('errors on invalid operator position', () => { - - expect(() => new Formula.Parser('x + + y')).to.throw('Formula contains an operator in invalid position'); - expect(() => new Formula.Parser('x + y +')).to.throw('Formula contains invalid trailing operator'); + expect(() => new Formula.Parser('x + + y')).toThrow('Formula contains an operator in invalid position'); + expect(() => new Formula.Parser('x + y +')).toThrow('Formula contains invalid trailing operator'); }); it('errors on invalid operator', () => { - - expect(() => new Formula.Parser('x | y')).to.throw('Formula contains an unknown operator |'); + expect(() => new Formula.Parser('x | y')).toThrow('Formula contains an unknown operator |'); }); it('errors on invalid missing operator', () => { - - expect(() => new Formula.Parser('x y')).to.throw('Formula missing expected operator'); + expect(() => new Formula.Parser('x y')).toThrow('Formula missing expected operator'); }); }); describe('_subFormula', () => { - it('parses multiple nested functions with arguments (numbers)', () => { - const functions = { x: (a, b, c) => a + b + c, y: (a, b) => a * b, z: (value) => value + 3, - a: () => 9 + a: () => 9, }; const formula = new Formula.Parser('x(10, (y((1 + 2), z(30)) + a()), z(x(4, 5, 6)))', { functions }); - expect(formula.evaluate()).to.equal(10 + (((1 + 2) * (30 + 3)) + 9) + (4 + 5 + 6) + 3); + expect(formula.evaluate()).toEqual(10 + ((1 + 2) * (30 + 3) + 9) + (4 + 5 + 6) + 3); }); it('parses multiple nested functions with arguments (strings)', () => { - const functions = { x: (a, b, c) => a + b + c, y: (a, b) => a + b, z: (value) => value + 3, - a: () => 9 + a: () => 9, }; - const formula = new Formula.Parser('x("10", (y(("1" + "2"), z("30")) + a()), z(x("4", "5", "6")))', { functions }); - expect(formula.evaluate()).to.equal('101230394563'); + const formula = new Formula.Parser('x("10", (y(("1" + "2"), z("30")) + a()), z(x("4", "5", "6")))', { + functions, + }); + expect(formula.evaluate()).toEqual('101230394563'); }); it('errors on unknown function', () => { - - expect(() => new Formula.Parser('x()')).to.throw('Formula contains unknown function x'); + expect(() => new Formula.Parser('x()')).toThrow('Formula contains unknown function x'); }); it('errors on invalid function arguments', () => { - - expect(() => new Formula.Parser('x(y,)', { functions: { x: () => null } })).to.throw('Formula contains function x with invalid arguments y,'); + expect(() => new Formula.Parser('x(y,)', { functions: { x: () => null } })).toThrow( + 'Formula contains function x with invalid arguments y,', + ); }); }); describe('evaluate()', () => { - it('handles null constant', () => { - const formula = new Formula.Parser('0 + null', { constants: { null: null } }); - expect(formula.evaluate()).to.equal(0); + expect(formula.evaluate()).toEqual(0); }); }); describe('single()', () => { - it('calculates -', () => { - const formula = new Formula.Parser('-x'); - expect(formula.evaluate({ x: 1 })).to.equal(-1); - expect(formula.evaluate({ x: -1 })).to.equal(1); - expect(formula.evaluate({ x: 0 })).to.equal(0); + expect(formula.evaluate({ x: 1 })).toEqual(-1); + expect(formula.evaluate({ x: -1 })).toEqual(1); + expect(formula.evaluate({ x: 0 })).toEqual(0); }); it('handles --', () => { - const formula = new Formula.Parser('10--x'); - expect(formula.evaluate({ x: 1 })).to.equal(11); - expect(formula.evaluate({ x: -1 })).to.equal(9); - expect(formula.evaluate({ x: 0 })).to.equal(10); + expect(formula.evaluate({ x: 1 })).toEqual(11); + expect(formula.evaluate({ x: -1 })).toEqual(9); + expect(formula.evaluate({ x: 0 })).toEqual(10); }); it('calculates !', () => { - const formula = new Formula.Parser('!x'); - expect(formula.evaluate({ x: 1 })).to.equal(false); - expect(formula.evaluate({ x: -1 })).to.equal(false); - expect(formula.evaluate({ x: 0 })).to.equal(true); + expect(formula.evaluate({ x: 1 })).toEqual(false); + expect(formula.evaluate({ x: -1 })).toEqual(false); + expect(formula.evaluate({ x: 0 })).toEqual(true); }); }); describe('calculate()', () => { - it('calculates +', () => { - const formula = new Formula.Parser('x+y'); - expect(formula.evaluate({ x: 1, y: 2 })).to.equal(3); - expect(formula.evaluate({ y: 2 })).to.equal(2); - expect(formula.evaluate({ x: 1 })).to.equal(1); - expect(formula.evaluate({ x: null, y: 2 })).to.equal(2); - expect(formula.evaluate({ x: 1, y: null })).to.equal(1); + expect(formula.evaluate({ x: 1, y: 2 })).toEqual(3); + expect(formula.evaluate({ y: 2 })).toEqual(2); + expect(formula.evaluate({ x: 1 })).toEqual(1); + expect(formula.evaluate({ x: null, y: 2 })).toEqual(2); + expect(formula.evaluate({ x: 1, y: null })).toEqual(1); - expect(formula.evaluate({ x: '1', y: '2' })).to.equal('12'); - expect(formula.evaluate({ y: '2' })).to.equal('2'); - expect(formula.evaluate({ x: '1' })).to.equal('1'); - expect(formula.evaluate({ x: null, y: '2' })).to.equal('2'); - expect(formula.evaluate({ x: '1', y: null })).to.equal('1'); + expect(formula.evaluate({ x: '1', y: '2' })).toEqual('12'); + expect(formula.evaluate({ y: '2' })).toEqual('2'); + expect(formula.evaluate({ x: '1' })).toEqual('1'); + expect(formula.evaluate({ x: null, y: '2' })).toEqual('2'); + expect(formula.evaluate({ x: '1', y: null })).toEqual('1'); - expect(formula.evaluate({ x: 1, y: '2' })).to.equal('12'); - expect(formula.evaluate({ x: '1', y: 2 })).to.equal('12'); + expect(formula.evaluate({ x: 1, y: '2' })).toEqual('12'); + expect(formula.evaluate({ x: '1', y: 2 })).toEqual('12'); }); it('calculates -', () => { - const formula = new Formula.Parser('x-y'); - expect(formula.evaluate({ x: 1, y: 2 })).to.equal(-1); - expect(formula.evaluate({ y: 2 })).to.equal(-2); - expect(formula.evaluate({ x: 1 })).to.equal(1); - expect(formula.evaluate({ x: null, y: 2 })).to.equal(-2); - expect(formula.evaluate({ x: 1, y: null })).to.equal(1); + expect(formula.evaluate({ x: 1, y: 2 })).toEqual(-1); + expect(formula.evaluate({ y: 2 })).toEqual(-2); + expect(formula.evaluate({ x: 1 })).toEqual(1); + expect(formula.evaluate({ x: null, y: 2 })).toEqual(-2); + expect(formula.evaluate({ x: 1, y: null })).toEqual(1); - expect(formula.evaluate({ x: '1', y: '2' })).to.equal(null); - expect(formula.evaluate({ y: '2' })).to.equal(null); - expect(formula.evaluate({ x: '1' })).to.equal(null); - expect(formula.evaluate({ x: null, y: '2' })).to.equal(null); - expect(formula.evaluate({ x: '1', y: null })).to.equal(null); + expect(formula.evaluate({ x: '1', y: '2' })).toEqual(null); + expect(formula.evaluate({ y: '2' })).toEqual(null); + expect(formula.evaluate({ x: '1' })).toEqual(null); + expect(formula.evaluate({ x: null, y: '2' })).toEqual(null); + expect(formula.evaluate({ x: '1', y: null })).toEqual(null); - expect(formula.evaluate({ x: 1, y: '2' })).to.equal(null); - expect(formula.evaluate({ x: '1', y: 2 })).to.equal(null); + expect(formula.evaluate({ x: 1, y: '2' })).toEqual(null); + expect(formula.evaluate({ x: '1', y: 2 })).toEqual(null); }); it('calculates *', () => { - const formula = new Formula.Parser('x*y'); - expect(formula.evaluate({ x: 20, y: 10 })).to.equal(200); - expect(formula.evaluate({ y: 2 })).to.equal(0); - expect(formula.evaluate({ x: 1 })).to.equal(0); - expect(formula.evaluate({ x: null, y: 2 })).to.equal(0); - expect(formula.evaluate({ x: 1, y: null })).to.equal(0); + expect(formula.evaluate({ x: 20, y: 10 })).toEqual(200); + expect(formula.evaluate({ y: 2 })).toEqual(0); + expect(formula.evaluate({ x: 1 })).toEqual(0); + expect(formula.evaluate({ x: null, y: 2 })).toEqual(0); + expect(formula.evaluate({ x: 1, y: null })).toEqual(0); - expect(formula.evaluate({ x: '1', y: '2' })).to.equal(null); - expect(formula.evaluate({ y: '2' })).to.equal(null); - expect(formula.evaluate({ x: '1' })).to.equal(null); - expect(formula.evaluate({ x: null, y: '2' })).to.equal(null); - expect(formula.evaluate({ x: '1', y: null })).to.equal(null); + expect(formula.evaluate({ x: '1', y: '2' })).toEqual(null); + expect(formula.evaluate({ y: '2' })).toEqual(null); + expect(formula.evaluate({ x: '1' })).toEqual(null); + expect(formula.evaluate({ x: null, y: '2' })).toEqual(null); + expect(formula.evaluate({ x: '1', y: null })).toEqual(null); - expect(formula.evaluate({ x: 1, y: '2' })).to.equal(null); - expect(formula.evaluate({ x: '1', y: 2 })).to.equal(null); + expect(formula.evaluate({ x: 1, y: '2' })).toEqual(null); + expect(formula.evaluate({ x: '1', y: 2 })).toEqual(null); }); it('calculates /', () => { - const formula = new Formula.Parser('x/y'); - expect(formula.evaluate({ x: 20, y: 10 })).to.equal(2); - expect(formula.evaluate({ y: 2 })).to.equal(0); - expect(formula.evaluate({ x: 1 })).to.equal(Infinity); - expect(formula.evaluate({ x: null, y: 2 })).to.equal(0); - expect(formula.evaluate({ x: 1, y: null })).to.equal(Infinity); + expect(formula.evaluate({ x: 20, y: 10 })).toEqual(2); + expect(formula.evaluate({ y: 2 })).toEqual(0); + expect(formula.evaluate({ x: 1 })).toEqual(Infinity); + expect(formula.evaluate({ x: null, y: 2 })).toEqual(0); + expect(formula.evaluate({ x: 1, y: null })).toEqual(Infinity); - expect(formula.evaluate({ x: '1', y: '2' })).to.equal(null); - expect(formula.evaluate({ y: '2' })).to.equal(null); - expect(formula.evaluate({ x: '1' })).to.equal(null); - expect(formula.evaluate({ x: null, y: '2' })).to.equal(null); - expect(formula.evaluate({ x: '1', y: null })).to.equal(null); + expect(formula.evaluate({ x: '1', y: '2' })).toEqual(null); + expect(formula.evaluate({ y: '2' })).toEqual(null); + expect(formula.evaluate({ x: '1' })).toEqual(null); + expect(formula.evaluate({ x: null, y: '2' })).toEqual(null); + expect(formula.evaluate({ x: '1', y: null })).toEqual(null); - expect(formula.evaluate({ x: 1, y: '2' })).to.equal(null); - expect(formula.evaluate({ x: '1', y: 2 })).to.equal(null); + expect(formula.evaluate({ x: 1, y: '2' })).toEqual(null); + expect(formula.evaluate({ x: '1', y: 2 })).toEqual(null); }); it('calculates ^', () => { - const formula = new Formula.Parser('x^y'); - expect(formula.evaluate({ x: 2, y: 3 })).to.equal(8); - expect(formula.evaluate({ y: 2 })).to.equal(0); - expect(formula.evaluate({ x: 1 })).to.equal(1); - expect(formula.evaluate({ x: null, y: 2 })).to.equal(0); - expect(formula.evaluate({ x: 1, y: null })).to.equal(1); + expect(formula.evaluate({ x: 2, y: 3 })).toEqual(8); + expect(formula.evaluate({ y: 2 })).toEqual(0); + expect(formula.evaluate({ x: 1 })).toEqual(1); + expect(formula.evaluate({ x: null, y: 2 })).toEqual(0); + expect(formula.evaluate({ x: 1, y: null })).toEqual(1); - expect(formula.evaluate({ x: '1', y: '2' })).to.equal(null); - expect(formula.evaluate({ y: '2' })).to.equal(null); - expect(formula.evaluate({ x: '1' })).to.equal(null); - expect(formula.evaluate({ x: null, y: '2' })).to.equal(null); - expect(formula.evaluate({ x: '1', y: null })).to.equal(null); + expect(formula.evaluate({ x: '1', y: '2' })).toEqual(null); + expect(formula.evaluate({ y: '2' })).toEqual(null); + expect(formula.evaluate({ x: '1' })).toEqual(null); + expect(formula.evaluate({ x: null, y: '2' })).toEqual(null); + expect(formula.evaluate({ x: '1', y: null })).toEqual(null); - expect(formula.evaluate({ x: 1, y: '2' })).to.equal(null); - expect(formula.evaluate({ x: '1', y: 2 })).to.equal(null); + expect(formula.evaluate({ x: 1, y: '2' })).toEqual(null); + expect(formula.evaluate({ x: '1', y: 2 })).toEqual(null); }); it('calculates %', () => { - const formula = new Formula.Parser('x%y'); - expect(formula.evaluate({ x: 10, y: 3 })).to.equal(1); - expect(formula.evaluate({ y: 2 })).to.equal(0); - expect(formula.evaluate({ x: 1 })).to.equal(NaN); - expect(formula.evaluate({ x: null, y: 2 })).to.equal(0); - expect(formula.evaluate({ x: 1, y: null })).to.equal(NaN); + expect(formula.evaluate({ x: 10, y: 3 })).toEqual(1); + expect(formula.evaluate({ y: 2 })).toEqual(0); + expect(formula.evaluate({ x: 1 })).toEqual(NaN); + expect(formula.evaluate({ x: null, y: 2 })).toEqual(0); + expect(formula.evaluate({ x: 1, y: null })).toEqual(NaN); - expect(formula.evaluate({ x: '1', y: '2' })).to.equal(null); - expect(formula.evaluate({ y: '2' })).to.equal(null); - expect(formula.evaluate({ x: '1' })).to.equal(null); - expect(formula.evaluate({ x: null, y: '2' })).to.equal(null); - expect(formula.evaluate({ x: '1', y: null })).to.equal(null); + expect(formula.evaluate({ x: '1', y: '2' })).toEqual(null); + expect(formula.evaluate({ y: '2' })).toEqual(null); + expect(formula.evaluate({ x: '1' })).toEqual(null); + expect(formula.evaluate({ x: null, y: '2' })).toEqual(null); + expect(formula.evaluate({ x: '1', y: null })).toEqual(null); - expect(formula.evaluate({ x: 1, y: '2' })).to.equal(null); - expect(formula.evaluate({ x: '1', y: 2 })).to.equal(null); + expect(formula.evaluate({ x: 1, y: '2' })).toEqual(null); + expect(formula.evaluate({ x: '1', y: 2 })).toEqual(null); }); it('compares <', () => { - const formula = new Formula.Parser('x', () => { - const formula = new Formula.Parser('x>y'); - expect(formula.evaluate({ x: 10, y: 3 })).to.equal(true); - expect(formula.evaluate({ x: 10, y: 10 })).to.equal(false); - expect(formula.evaluate({ y: 2 })).to.equal(false); - expect(formula.evaluate({ x: 1 })).to.equal(true); - expect(formula.evaluate({ x: null, y: 2 })).to.equal(false); - expect(formula.evaluate({ x: 1, y: null })).to.equal(true); + expect(formula.evaluate({ x: 10, y: 3 })).toEqual(true); + expect(formula.evaluate({ x: 10, y: 10 })).toEqual(false); + expect(formula.evaluate({ y: 2 })).toEqual(false); + expect(formula.evaluate({ x: 1 })).toEqual(true); + expect(formula.evaluate({ x: null, y: 2 })).toEqual(false); + expect(formula.evaluate({ x: 1, y: null })).toEqual(true); - expect(formula.evaluate({ x: '1', y: '2' })).to.equal(false); - expect(formula.evaluate({ x: '1', y: '1' })).to.equal(false); - expect(formula.evaluate({ y: '2' })).to.equal(false); - expect(formula.evaluate({ x: '1' })).to.equal(true); - expect(formula.evaluate({ x: null, y: '2' })).to.equal(false); - expect(formula.evaluate({ x: '1', y: null })).to.equal(true); + expect(formula.evaluate({ x: '1', y: '2' })).toEqual(false); + expect(formula.evaluate({ x: '1', y: '1' })).toEqual(false); + expect(formula.evaluate({ y: '2' })).toEqual(false); + expect(formula.evaluate({ x: '1' })).toEqual(true); + expect(formula.evaluate({ x: null, y: '2' })).toEqual(false); + expect(formula.evaluate({ x: '1', y: null })).toEqual(true); - expect(formula.evaluate({ x: 1, y: '2' })).to.equal(false); - expect(formula.evaluate({ x: '1', y: 2 })).to.equal(false); - expect(formula.evaluate({ x: 1, y: '1' })).to.equal(false); - expect(formula.evaluate({ x: '1', y: 1 })).to.equal(false); + expect(formula.evaluate({ x: 1, y: '2' })).toEqual(false); + expect(formula.evaluate({ x: '1', y: 2 })).toEqual(false); + expect(formula.evaluate({ x: 1, y: '1' })).toEqual(false); + expect(formula.evaluate({ x: '1', y: 1 })).toEqual(false); - expect(formula.evaluate({ x: null, y: null })).to.equal(false); - expect(formula.evaluate({ y: null })).to.equal(false); - expect(formula.evaluate({ x: null })).to.equal(false); - expect(formula.evaluate()).to.equal(false); + expect(formula.evaluate({ x: null, y: null })).toEqual(false); + expect(formula.evaluate({ y: null })).toEqual(false); + expect(formula.evaluate({ x: null })).toEqual(false); + expect(formula.evaluate()).toEqual(false); }); it('compares <=', () => { - const formula = new Formula.Parser('x<=y'); - expect(formula.evaluate({ x: 10, y: 3 })).to.equal(false); - expect(formula.evaluate({ x: 10, y: 10 })).to.equal(true); - expect(formula.evaluate({ y: 2 })).to.equal(true); - expect(formula.evaluate({ x: 1 })).to.equal(false); - expect(formula.evaluate({ x: null, y: 2 })).to.equal(true); - expect(formula.evaluate({ x: 1, y: null })).to.equal(false); + expect(formula.evaluate({ x: 10, y: 3 })).toEqual(false); + expect(formula.evaluate({ x: 10, y: 10 })).toEqual(true); + expect(formula.evaluate({ y: 2 })).toEqual(true); + expect(formula.evaluate({ x: 1 })).toEqual(false); + expect(formula.evaluate({ x: null, y: 2 })).toEqual(true); + expect(formula.evaluate({ x: 1, y: null })).toEqual(false); - expect(formula.evaluate({ x: '1', y: '2' })).to.equal(true); - expect(formula.evaluate({ x: '1', y: '1' })).to.equal(true); - expect(formula.evaluate({ y: '2' })).to.equal(true); - expect(formula.evaluate({ x: '1' })).to.equal(false); - expect(formula.evaluate({ x: null, y: '2' })).to.equal(true); - expect(formula.evaluate({ x: '1', y: null })).to.equal(false); + expect(formula.evaluate({ x: '1', y: '2' })).toEqual(true); + expect(formula.evaluate({ x: '1', y: '1' })).toEqual(true); + expect(formula.evaluate({ y: '2' })).toEqual(true); + expect(formula.evaluate({ x: '1' })).toEqual(false); + expect(formula.evaluate({ x: null, y: '2' })).toEqual(true); + expect(formula.evaluate({ x: '1', y: null })).toEqual(false); - expect(formula.evaluate({ x: 1, y: '2' })).to.equal(true); - expect(formula.evaluate({ x: '1', y: 2 })).to.equal(true); - expect(formula.evaluate({ x: 1, y: '1' })).to.equal(true); - expect(formula.evaluate({ x: '1', y: 1 })).to.equal(true); + expect(formula.evaluate({ x: 1, y: '2' })).toEqual(true); + expect(formula.evaluate({ x: '1', y: 2 })).toEqual(true); + expect(formula.evaluate({ x: 1, y: '1' })).toEqual(true); + expect(formula.evaluate({ x: '1', y: 1 })).toEqual(true); - expect(formula.evaluate({ x: null, y: null })).to.equal(true); - expect(formula.evaluate({ y: null })).to.equal(true); - expect(formula.evaluate({ x: null })).to.equal(true); - expect(formula.evaluate()).to.equal(true); + expect(formula.evaluate({ x: null, y: null })).toEqual(true); + expect(formula.evaluate({ y: null })).toEqual(true); + expect(formula.evaluate({ x: null })).toEqual(true); + expect(formula.evaluate()).toEqual(true); }); it('compares >=', () => { - const formula = new Formula.Parser('x>=y'); - expect(formula.evaluate({ x: 10, y: 3 })).to.equal(true); - expect(formula.evaluate({ x: 10, y: 10 })).to.equal(true); - expect(formula.evaluate({ y: 2 })).to.equal(false); - expect(formula.evaluate({ x: 1 })).to.equal(true); - expect(formula.evaluate({ x: null, y: 2 })).to.equal(false); - expect(formula.evaluate({ x: 1, y: null })).to.equal(true); + expect(formula.evaluate({ x: 10, y: 3 })).toEqual(true); + expect(formula.evaluate({ x: 10, y: 10 })).toEqual(true); + expect(formula.evaluate({ y: 2 })).toEqual(false); + expect(formula.evaluate({ x: 1 })).toEqual(true); + expect(formula.evaluate({ x: null, y: 2 })).toEqual(false); + expect(formula.evaluate({ x: 1, y: null })).toEqual(true); - expect(formula.evaluate({ x: '1', y: '2' })).to.equal(false); - expect(formula.evaluate({ x: '1', y: '1' })).to.equal(true); - expect(formula.evaluate({ y: '2' })).to.equal(false); - expect(formula.evaluate({ x: '1' })).to.equal(true); - expect(formula.evaluate({ x: null, y: '2' })).to.equal(false); - expect(formula.evaluate({ x: '1', y: null })).to.equal(true); + expect(formula.evaluate({ x: '1', y: '2' })).toEqual(false); + expect(formula.evaluate({ x: '1', y: '1' })).toEqual(true); + expect(formula.evaluate({ y: '2' })).toEqual(false); + expect(formula.evaluate({ x: '1' })).toEqual(true); + expect(formula.evaluate({ x: null, y: '2' })).toEqual(false); + expect(formula.evaluate({ x: '1', y: null })).toEqual(true); - expect(formula.evaluate({ x: 1, y: '2' })).to.equal(false); - expect(formula.evaluate({ x: '1', y: 2 })).to.equal(false); - expect(formula.evaluate({ x: 1, y: '1' })).to.equal(true); - expect(formula.evaluate({ x: '1', y: 1 })).to.equal(true); + expect(formula.evaluate({ x: 1, y: '2' })).toEqual(false); + expect(formula.evaluate({ x: '1', y: 2 })).toEqual(false); + expect(formula.evaluate({ x: 1, y: '1' })).toEqual(true); + expect(formula.evaluate({ x: '1', y: 1 })).toEqual(true); - expect(formula.evaluate({ x: null, y: null })).to.equal(true); - expect(formula.evaluate({ y: null })).to.equal(true); - expect(formula.evaluate({ x: null })).to.equal(true); - expect(formula.evaluate()).to.equal(true); + expect(formula.evaluate({ x: null, y: null })).toEqual(true); + expect(formula.evaluate({ y: null })).toEqual(true); + expect(formula.evaluate({ x: null })).toEqual(true); + expect(formula.evaluate()).toEqual(true); }); it('compares ==', () => { - const formula = new Formula.Parser('x==y'); - expect(formula.evaluate({ x: 10, y: 3 })).to.equal(false); - expect(formula.evaluate({ x: 10, y: 10 })).to.equal(true); - expect(formula.evaluate({ y: 2 })).to.equal(false); - expect(formula.evaluate({ x: 1 })).to.equal(false); - expect(formula.evaluate({ x: null, y: 2 })).to.equal(false); - expect(formula.evaluate({ x: 1, y: null })).to.equal(false); + expect(formula.evaluate({ x: 10, y: 3 })).toEqual(false); + expect(formula.evaluate({ x: 10, y: 10 })).toEqual(true); + expect(formula.evaluate({ y: 2 })).toEqual(false); + expect(formula.evaluate({ x: 1 })).toEqual(false); + expect(formula.evaluate({ x: null, y: 2 })).toEqual(false); + expect(formula.evaluate({ x: 1, y: null })).toEqual(false); - expect(formula.evaluate({ x: '1', y: '2' })).to.equal(false); - expect(formula.evaluate({ x: '1', y: '1' })).to.equal(true); - expect(formula.evaluate({ y: '2' })).to.equal(false); - expect(formula.evaluate({ x: '1' })).to.equal(false); - expect(formula.evaluate({ x: null, y: '2' })).to.equal(false); - expect(formula.evaluate({ x: '1', y: null })).to.equal(false); + expect(formula.evaluate({ x: '1', y: '2' })).toEqual(false); + expect(formula.evaluate({ x: '1', y: '1' })).toEqual(true); + expect(formula.evaluate({ y: '2' })).toEqual(false); + expect(formula.evaluate({ x: '1' })).toEqual(false); + expect(formula.evaluate({ x: null, y: '2' })).toEqual(false); + expect(formula.evaluate({ x: '1', y: null })).toEqual(false); - expect(formula.evaluate({ x: 1, y: '2' })).to.equal(false); - expect(formula.evaluate({ x: '1', y: 2 })).to.equal(false); - expect(formula.evaluate({ x: 1, y: '1' })).to.equal(false); - expect(formula.evaluate({ x: '1', y: 1 })).to.equal(false); + expect(formula.evaluate({ x: 1, y: '2' })).toEqual(false); + expect(formula.evaluate({ x: '1', y: 2 })).toEqual(false); + expect(formula.evaluate({ x: 1, y: '1' })).toEqual(false); + expect(formula.evaluate({ x: '1', y: 1 })).toEqual(false); - expect(formula.evaluate({ x: null, y: null })).to.equal(true); - expect(formula.evaluate({ y: null })).to.equal(true); - expect(formula.evaluate({ x: null })).to.equal(true); - expect(formula.evaluate()).to.equal(true); + expect(formula.evaluate({ x: null, y: null })).toEqual(true); + expect(formula.evaluate({ y: null })).toEqual(true); + expect(formula.evaluate({ x: null })).toEqual(true); + expect(formula.evaluate()).toEqual(true); }); it('compares !=', () => { - const formula = new Formula.Parser('x!=y'); - expect(formula.evaluate({ x: 10, y: 3 })).to.equal(true); - expect(formula.evaluate({ x: 10, y: 10 })).to.equal(false); - expect(formula.evaluate({ y: 2 })).to.equal(true); - expect(formula.evaluate({ x: 1 })).to.equal(true); - expect(formula.evaluate({ x: null, y: 2 })).to.equal(true); - expect(formula.evaluate({ x: 1, y: null })).to.equal(true); + expect(formula.evaluate({ x: 10, y: 3 })).toEqual(true); + expect(formula.evaluate({ x: 10, y: 10 })).toEqual(false); + expect(formula.evaluate({ y: 2 })).toEqual(true); + expect(formula.evaluate({ x: 1 })).toEqual(true); + expect(formula.evaluate({ x: null, y: 2 })).toEqual(true); + expect(formula.evaluate({ x: 1, y: null })).toEqual(true); - expect(formula.evaluate({ x: '1', y: '2' })).to.equal(true); - expect(formula.evaluate({ x: '1', y: '1' })).to.equal(false); - expect(formula.evaluate({ y: '2' })).to.equal(true); - expect(formula.evaluate({ x: '1' })).to.equal(true); - expect(formula.evaluate({ x: null, y: '2' })).to.equal(true); - expect(formula.evaluate({ x: '1', y: null })).to.equal(true); + expect(formula.evaluate({ x: '1', y: '2' })).toEqual(true); + expect(formula.evaluate({ x: '1', y: '1' })).toEqual(false); + expect(formula.evaluate({ y: '2' })).toEqual(true); + expect(formula.evaluate({ x: '1' })).toEqual(true); + expect(formula.evaluate({ x: null, y: '2' })).toEqual(true); + expect(formula.evaluate({ x: '1', y: null })).toEqual(true); - expect(formula.evaluate({ x: 1, y: '2' })).to.equal(true); - expect(formula.evaluate({ x: '1', y: 2 })).to.equal(true); - expect(formula.evaluate({ x: 1, y: '1' })).to.equal(true); - expect(formula.evaluate({ x: '1', y: 1 })).to.equal(true); + expect(formula.evaluate({ x: 1, y: '2' })).toEqual(true); + expect(formula.evaluate({ x: '1', y: 2 })).toEqual(true); + expect(formula.evaluate({ x: 1, y: '1' })).toEqual(true); + expect(formula.evaluate({ x: '1', y: 1 })).toEqual(true); - expect(formula.evaluate({ x: null, y: null })).to.equal(false); - expect(formula.evaluate({ y: null })).to.equal(false); - expect(formula.evaluate({ x: null })).to.equal(false); - expect(formula.evaluate()).to.equal(false); + expect(formula.evaluate({ x: null, y: null })).toEqual(false); + expect(formula.evaluate({ y: null })).toEqual(false); + expect(formula.evaluate({ x: null })).toEqual(false); + expect(formula.evaluate()).toEqual(false); }); it('applies logical ||', () => { - const formula = new Formula.Parser('x || y'); - expect(formula.evaluate({ x: 10, y: 3 })).to.equal(10); - expect(formula.evaluate({ x: 10, y: 10 })).to.equal(10); - expect(formula.evaluate({ x: 0, y: 10 })).to.equal(10); - expect(formula.evaluate({ x: 0, y: 0 })).to.equal(0); - expect(formula.evaluate({ y: 2 })).to.equal(2); - expect(formula.evaluate({ x: 1 })).to.equal(1); - expect(formula.evaluate({ x: null, y: 2 })).to.equal(2); - expect(formula.evaluate({ x: 1, y: null })).to.equal(1); - - expect(formula.evaluate({ x: '1', y: '2' })).to.equal('1'); - expect(formula.evaluate({ x: '1', y: '1' })).to.equal('1'); - expect(formula.evaluate({ x: '', y: '1' })).to.equal('1'); - expect(formula.evaluate({ x: '', y: '' })).to.equal(''); - expect(formula.evaluate({ y: '2' })).to.equal('2'); - expect(formula.evaluate({ x: '1' })).to.equal('1'); - expect(formula.evaluate({ x: null, y: '2' })).to.equal('2'); - expect(formula.evaluate({ x: '1', y: null })).to.equal('1'); - - expect(formula.evaluate({ x: 1, y: '2' })).to.equal(1); - expect(formula.evaluate({ x: '1', y: 2 })).to.equal('1'); - expect(formula.evaluate({ x: 1, y: '1' })).to.equal(1); - expect(formula.evaluate({ x: '1', y: 1 })).to.equal('1'); - - expect(formula.evaluate({ x: null, y: null })).to.equal(null); - expect(formula.evaluate({ y: null })).to.equal(null); - expect(formula.evaluate({ x: null })).to.equal(null); - expect(formula.evaluate()).to.equal(null); + expect(formula.evaluate({ x: 10, y: 3 })).toEqual(10); + expect(formula.evaluate({ x: 10, y: 10 })).toEqual(10); + expect(formula.evaluate({ x: 0, y: 10 })).toEqual(10); + expect(formula.evaluate({ x: 0, y: 0 })).toEqual(0); + expect(formula.evaluate({ y: 2 })).toEqual(2); + expect(formula.evaluate({ x: 1 })).toEqual(1); + expect(formula.evaluate({ x: null, y: 2 })).toEqual(2); + expect(formula.evaluate({ x: 1, y: null })).toEqual(1); + + expect(formula.evaluate({ x: '1', y: '2' })).toEqual('1'); + expect(formula.evaluate({ x: '1', y: '1' })).toEqual('1'); + expect(formula.evaluate({ x: '', y: '1' })).toEqual('1'); + expect(formula.evaluate({ x: '', y: '' })).toEqual(''); + expect(formula.evaluate({ y: '2' })).toEqual('2'); + expect(formula.evaluate({ x: '1' })).toEqual('1'); + expect(formula.evaluate({ x: null, y: '2' })).toEqual('2'); + expect(formula.evaluate({ x: '1', y: null })).toEqual('1'); + + expect(formula.evaluate({ x: 1, y: '2' })).toEqual(1); + expect(formula.evaluate({ x: '1', y: 2 })).toEqual('1'); + expect(formula.evaluate({ x: 1, y: '1' })).toEqual(1); + expect(formula.evaluate({ x: '1', y: 1 })).toEqual('1'); + + expect(formula.evaluate({ x: null, y: null })).toEqual(null); + expect(formula.evaluate({ y: null })).toEqual(null); + expect(formula.evaluate({ x: null })).toEqual(null); + expect(formula.evaluate()).toEqual(null); }); it('applies logical &&', () => { - const formula = new Formula.Parser('x && y'); - expect(formula.evaluate({ x: 10, y: 3 })).to.equal(3); - expect(formula.evaluate({ x: 10, y: 10 })).to.equal(10); - expect(formula.evaluate({ x: 0, y: 10 })).to.equal(0); - expect(formula.evaluate({ x: 0, y: 0 })).to.equal(0); - expect(formula.evaluate({ y: 2 })).to.equal(null); - expect(formula.evaluate({ x: 1 })).to.equal(null); - expect(formula.evaluate({ x: null, y: 2 })).to.equal(null); - expect(formula.evaluate({ x: 1, y: null })).to.equal(null); - - expect(formula.evaluate({ x: '1', y: '2' })).to.equal('2'); - expect(formula.evaluate({ x: '1', y: '1' })).to.equal('1'); - expect(formula.evaluate({ x: '', y: '1' })).to.equal(''); - expect(formula.evaluate({ x: '', y: '' })).to.equal(''); - expect(formula.evaluate({ y: '2' })).to.equal(null); - expect(formula.evaluate({ x: '1' })).to.equal(null); - expect(formula.evaluate({ x: null, y: '2' })).to.equal(null); - expect(formula.evaluate({ x: '1', y: null })).to.equal(null); - - expect(formula.evaluate({ x: 1, y: '2' })).to.equal('2'); - expect(formula.evaluate({ x: '1', y: 2 })).to.equal(2); - expect(formula.evaluate({ x: 1, y: '1' })).to.equal('1'); - expect(formula.evaluate({ x: '1', y: 1 })).to.equal(1); - - expect(formula.evaluate({ x: null, y: null })).to.equal(null); - expect(formula.evaluate({ y: null })).to.equal(null); - expect(formula.evaluate({ x: null })).to.equal(null); - expect(formula.evaluate()).to.equal(null); + expect(formula.evaluate({ x: 10, y: 3 })).toEqual(3); + expect(formula.evaluate({ x: 10, y: 10 })).toEqual(10); + expect(formula.evaluate({ x: 0, y: 10 })).toEqual(0); + expect(formula.evaluate({ x: 0, y: 0 })).toEqual(0); + expect(formula.evaluate({ y: 2 })).toEqual(null); + expect(formula.evaluate({ x: 1 })).toEqual(null); + expect(formula.evaluate({ x: null, y: 2 })).toEqual(null); + expect(formula.evaluate({ x: 1, y: null })).toEqual(null); + + expect(formula.evaluate({ x: '1', y: '2' })).toEqual('2'); + expect(formula.evaluate({ x: '1', y: '1' })).toEqual('1'); + expect(formula.evaluate({ x: '', y: '1' })).toEqual(''); + expect(formula.evaluate({ x: '', y: '' })).toEqual(''); + expect(formula.evaluate({ y: '2' })).toEqual(null); + expect(formula.evaluate({ x: '1' })).toEqual(null); + expect(formula.evaluate({ x: null, y: '2' })).toEqual(null); + expect(formula.evaluate({ x: '1', y: null })).toEqual(null); + + expect(formula.evaluate({ x: 1, y: '2' })).toEqual('2'); + expect(formula.evaluate({ x: '1', y: 2 })).toEqual(2); + expect(formula.evaluate({ x: 1, y: '1' })).toEqual('1'); + expect(formula.evaluate({ x: '1', y: 1 })).toEqual(1); + + expect(formula.evaluate({ x: null, y: null })).toEqual(null); + expect(formula.evaluate({ y: null })).toEqual(null); + expect(formula.evaluate({ x: null })).toEqual(null); + expect(formula.evaluate()).toEqual(null); }); it('applies logical ??', () => { - const formula = new Formula.Parser('x ?? y'); - expect(formula.evaluate({ x: 10, y: 3 })).to.equal(10); - expect(formula.evaluate({ x: 10, y: 10 })).to.equal(10); - expect(formula.evaluate({ x: 0, y: 10 })).to.equal(0); - expect(formula.evaluate({ x: 0, y: 0 })).to.equal(0); - expect(formula.evaluate({ y: 2 })).to.equal(2); - expect(formula.evaluate({ x: 1 })).to.equal(1); - expect(formula.evaluate({ x: null, y: 2 })).to.equal(2); - expect(formula.evaluate({ x: 1, y: null })).to.equal(1); - - expect(formula.evaluate({ x: '1', y: '2' })).to.equal('1'); - expect(formula.evaluate({ x: '1', y: '1' })).to.equal('1'); - expect(formula.evaluate({ x: '', y: '1' })).to.equal(''); - expect(formula.evaluate({ x: '', y: '' })).to.equal(''); - expect(formula.evaluate({ y: '2' })).to.equal('2'); - expect(formula.evaluate({ x: '1' })).to.equal('1'); - expect(formula.evaluate({ x: null, y: '2' })).to.equal('2'); - expect(formula.evaluate({ x: '1', y: null })).to.equal('1'); - - expect(formula.evaluate({ x: 1, y: '2' })).to.equal(1); - expect(formula.evaluate({ x: '1', y: 2 })).to.equal('1'); - expect(formula.evaluate({ x: 1, y: '1' })).to.equal(1); - expect(formula.evaluate({ x: '1', y: 1 })).to.equal('1'); - - expect(formula.evaluate({ x: null, y: null })).to.equal(null); - expect(formula.evaluate({ y: null })).to.equal(null); - expect(formula.evaluate({ x: null })).to.equal(null); - expect(formula.evaluate()).to.equal(null); + expect(formula.evaluate({ x: 10, y: 3 })).toEqual(10); + expect(formula.evaluate({ x: 10, y: 10 })).toEqual(10); + expect(formula.evaluate({ x: 0, y: 10 })).toEqual(0); + expect(formula.evaluate({ x: 0, y: 0 })).toEqual(0); + expect(formula.evaluate({ y: 2 })).toEqual(2); + expect(formula.evaluate({ x: 1 })).toEqual(1); + expect(formula.evaluate({ x: null, y: 2 })).toEqual(2); + expect(formula.evaluate({ x: 1, y: null })).toEqual(1); + + expect(formula.evaluate({ x: '1', y: '2' })).toEqual('1'); + expect(formula.evaluate({ x: '1', y: '1' })).toEqual('1'); + expect(formula.evaluate({ x: '', y: '1' })).toEqual(''); + expect(formula.evaluate({ x: '', y: '' })).toEqual(''); + expect(formula.evaluate({ y: '2' })).toEqual('2'); + expect(formula.evaluate({ x: '1' })).toEqual('1'); + expect(formula.evaluate({ x: null, y: '2' })).toEqual('2'); + expect(formula.evaluate({ x: '1', y: null })).toEqual('1'); + + expect(formula.evaluate({ x: 1, y: '2' })).toEqual(1); + expect(formula.evaluate({ x: '1', y: 2 })).toEqual('1'); + expect(formula.evaluate({ x: 1, y: '1' })).toEqual(1); + expect(formula.evaluate({ x: '1', y: 1 })).toEqual('1'); + + expect(formula.evaluate({ x: null, y: null })).toEqual(null); + expect(formula.evaluate({ y: null })).toEqual(null); + expect(formula.evaluate({ x: null })).toEqual(null); + expect(formula.evaluate()).toEqual(null); }); }); }); diff --git a/test/index.ts b/test/index.ts deleted file mode 100755 index 0003368..0000000 --- a/test/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import * as Formula from '..'; -import * as Lab from '@hapi/lab'; - - -const { expect } = Lab.types; - - -// new Formula.Parser() - -const options = { - - functions: { - x: (value: number): number => value + 10 - }, - - constants: { - Z: 100 - }, - - reference: ((name) => (context) => context[name]) as Formula.Options.Reference, - - tokenRx: /^[\w \+\.]+$/ -}; - -const parser = new Formula.Parser('1 + a.b.c.2.4.x + [b] + x([y + 4] + Z)', options); - -expect.error(new Formula.Parser()); -expect.error(new Formula.Parser(123)); -expect.error(new Formula.Parser('a', 123)); -expect.error(new Formula.Parser('a', { unknown: true })); - - -// parser.evaluate() - -parser.evaluate({ 'a.b.c.2.4.x': 2, b: 3, 'y + 4': 5 }); diff --git a/test/typings.ts b/test/typings.ts new file mode 100755 index 0000000..743b09d --- /dev/null +++ b/test/typings.ts @@ -0,0 +1,52 @@ +import { describe, expectTypeOf, it } from 'vitest'; + +import * as Formula from '../lib/index.js'; + +describe('typings', () => { + describe('Parser', () => { + it('constructor + evaluate compile', () => { + const options = { + functions: { + x: (value: number): number => value + 10, + }, + constants: { + Z: 100, + }, + reference: (name: string) => { + return (context: Record) => context[name]; + }, + tokenRx: /^[\w +.]+$/, + }; + + const parser = new Formula.Parser('1 + a.b.c.2.4.x + [b] + x([y + 4] + Z)', options); + expectTypeOf(parser).toHaveProperty('evaluate'); + expectTypeOf(parser.evaluate).toBeFunction(); + + // evaluate accepts a runtime context + parser.evaluate({ 'a.b.c.2.4.x': 2, b: 3, 'y + 4': 5 }); + }); + + it('reference factory satisfies Options.Reference', () => { + expectTypeOf().toBeFunction(); + const reference: Formula.Options.Reference = (name) => { + return (context) => context[name]; + }; + new Formula.Parser('x', { reference }); + }); + + it('invalid calls rejected', () => { + // The @ts-expect-error directives are the assertion; the calls still run, so + // wrap them — invalid args throw at runtime (the type errors are what we test). + try { + // @ts-expect-error formula string is required + new Formula.Parser(); + // @ts-expect-error formula must be a string + new Formula.Parser(123); + // @ts-expect-error options must be an object + new Formula.Parser('a', 123); + // @ts-expect-error unknown option key + new Formula.Parser('a', { unknown: true }); + } catch {} + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..849762f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "inlineSources": true, + "isolatedDeclarations": true, + "isolatedModules": true, + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "skipDefaultLibCheck": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "target": "ESNext" + } +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..df8ecc7 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,23 @@ +import Oxc from '@hapi/oxc-plugin/vitest'; +import { defineConfig } from 'vitest/config'; + +import type { ViteUserConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [Oxc()], + test: { + environment: 'node', + include: ['test/**/*.{js,ts}'], + typecheck: { + enabled: true, + include: ['test/**/*.{js,ts}'], + }, + coverage: { + provider: 'v8', + include: ['lib/**'], + thresholds: { + 100: true, + }, + }, + }, +}) as ViteUserConfig; From 38795effcbae8255ff200c041c4c16cd46d9cab5 Mon Sep 17 00:00:00 2001 From: Danilo Alonso Date: Fri, 26 Jun 2026 23:48:55 -0400 Subject: [PATCH 2/7] refactor: git mv source to src/ with .mjs extension Pure rename (no content change) so the move is tracked as a rename; reference fixups follow in the next commit. --- lib/index.d.ts => src/index.d.mts | 0 lib/index.js => src/index.mjs | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename lib/index.d.ts => src/index.d.mts (100%) rename lib/index.js => src/index.mjs (100%) diff --git a/lib/index.d.ts b/src/index.d.mts similarity index 100% rename from lib/index.d.ts rename to src/index.d.mts diff --git a/lib/index.js b/src/index.mjs similarity index 100% rename from lib/index.js rename to src/index.mjs From b300311afbc78558b940b007ffa4071cdf0734d1 Mon Sep 17 00:00:00 2001 From: Danilo Alonso Date: Fri, 26 Jun 2026 23:48:57 -0400 Subject: [PATCH 3/7] refactor: repoint exports, imports, and tests to src/*.mjs package.json exports/types -> src/index.mjs + src/index.d.mts; vitest coverage include -> src/**; test imports -> ../src/index.mjs; b64 also updates index.mjs internal re-exports. --- package.json | 8 ++++---- test/index.js | 2 +- test/typings.ts | 2 +- vitest.config.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 3f5badd..2dfc429 100755 --- a/package.json +++ b/package.json @@ -11,16 +11,16 @@ "license": "BSD-3-Clause", "repository": "git://github.com/hapijs/formula", "files": [ - "lib", + "src", "API.md", "README.md" ], "type": "module", - "types": "lib/index.d.ts", + "types": "src/index.d.mts", "exports": { ".": { - "types": "./lib/index.d.ts", - "default": "./lib/index.js" + "types": "./src/index.d.mts", + "default": "./src/index.mjs" } }, "scripts": { diff --git a/test/index.js b/test/index.js index 4dbd186..2f4809a 100755 --- a/test/index.js +++ b/test/index.js @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import * as Formula from '../lib/index.js'; +import * as Formula from '../src/index.mjs'; describe('Formula', () => { it('evaluates a formula', () => { diff --git a/test/typings.ts b/test/typings.ts index 743b09d..223c63e 100755 --- a/test/typings.ts +++ b/test/typings.ts @@ -1,6 +1,6 @@ import { describe, expectTypeOf, it } from 'vitest'; -import * as Formula from '../lib/index.js'; +import * as Formula from '../src/index.mjs'; describe('typings', () => { describe('Parser', () => { diff --git a/vitest.config.ts b/vitest.config.ts index df8ecc7..2420cb5 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -14,7 +14,7 @@ export default defineConfig({ }, coverage: { provider: 'v8', - include: ['lib/**'], + include: ['src/**'], thresholds: { 100: true, }, From 68aad22f406f8580154864bb42d0a1e2b0d39f9d Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 1 Jul 2026 14:51:54 +0200 Subject: [PATCH 4/7] chore: revert renaming --- package.json | 6 +++--- src/{index.d.mts => index.d.ts} | 0 src/{index.mjs => index.js} | 0 test/index.js | 2 +- test/typings.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename src/{index.d.mts => index.d.ts} (100%) rename src/{index.mjs => index.js} (100%) diff --git a/package.json b/package.json index 2dfc429..087f5b6 100755 --- a/package.json +++ b/package.json @@ -16,11 +16,11 @@ "README.md" ], "type": "module", - "types": "src/index.d.mts", + "types": "src/index.d.ts", "exports": { ".": { - "types": "./src/index.d.mts", - "default": "./src/index.mjs" + "types": "./src/index.d.ts", + "default": "./src/index.js" } }, "scripts": { diff --git a/src/index.d.mts b/src/index.d.ts similarity index 100% rename from src/index.d.mts rename to src/index.d.ts diff --git a/src/index.mjs b/src/index.js similarity index 100% rename from src/index.mjs rename to src/index.js diff --git a/test/index.js b/test/index.js index 2f4809a..bf013a3 100755 --- a/test/index.js +++ b/test/index.js @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import * as Formula from '../src/index.mjs'; +import * as Formula from '../src/index.js'; describe('Formula', () => { it('evaluates a formula', () => { diff --git a/test/typings.ts b/test/typings.ts index 223c63e..d47f608 100755 --- a/test/typings.ts +++ b/test/typings.ts @@ -1,6 +1,6 @@ import { describe, expectTypeOf, it } from 'vitest'; -import * as Formula from '../src/index.mjs'; +import * as Formula from '../src/index.js'; describe('typings', () => { describe('Parser', () => { From 5d6232ef2940d75d93f03bca93115b60027efe4a Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 1 Jul 2026 14:52:05 +0200 Subject: [PATCH 5/7] chore: update module name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 13da3af..4bde9b2 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# @sideway/formula +# @hapi/formula #### Math and string formula parser. From 7195412a1cd45d1d2c0fdeeab54fea779c02784e Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 1 Jul 2026 14:54:12 +0200 Subject: [PATCH 6/7] chore: upgrade linting and fix issues --- package.json | 4 ++-- src/index.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 087f5b6..8e7dcfa 100755 --- a/package.json +++ b/package.json @@ -35,8 +35,8 @@ "devDependencies": { "@hapi/oxc-plugin": "^1.0.1", "@vitest/coverage-v8": "^4.1.9", - "oxfmt": "^0.55.0", - "oxlint": "^1.70.0", + "oxfmt": "^0.57.0", + "oxlint": "^1.72.0", "typescript": "^6.0.3", "vitest": "^4.1.9" }, diff --git a/src/index.js b/src/index.js index 504660a..63d56d0 100755 --- a/src/index.js +++ b/src/index.js @@ -10,8 +10,8 @@ const literals = { '[': ']', }; -const numberRx = /^(?:[0-9]*(\.[0-9]*)?){1}$/; -const tokenRx = /^[\w\$\#\.\@\:\{\}]+$/; +const numberRx = /^(?:[0-9]*(\.[0-9]*)?)$/; +const tokenRx = /^[\w$#.@:{}]+$/; const symbol = Symbol('formula'); const settingsSymbol = Symbol('settings'); @@ -335,7 +335,7 @@ function evaluate(part, context) { function single(operator, value) { if (operator === '!') { - return value ? false : true; + return !value; } // operator === 'n' From ae377f8ab9aae3dd90089615a19f5ad1c3824c9a Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 1 Jul 2026 15:00:43 +0200 Subject: [PATCH 7/7] chore: remove README.md --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 8e7dcfa..b27907d 100755 --- a/package.json +++ b/package.json @@ -12,8 +12,7 @@ "repository": "git://github.com/hapijs/formula", "files": [ "src", - "API.md", - "README.md" + "API.md" ], "type": "module", "types": "src/index.d.ts",