Comparing version 1.0.0-beta.7 to 1.0.0-beta.8
{ | ||
"name": "jora", | ||
"version": "1.0.0-beta.7", | ||
"version": "1.0.0-beta.8", | ||
"description": "JavaScript object query engine", | ||
@@ -28,5 +28,5 @@ "author": "Roman Dvornov <rdvornov@gmail.com> (https://github.com/lahmatiy)", | ||
"scripts": { | ||
"lint": "eslint src test", | ||
"test": "mocha --reporter ${REPORTER:-progress}", | ||
"test:cjs": "mocha cjs-test --reporter ${REPORTER:-progress}", | ||
"lint": "eslint src test docs", | ||
"test": "mocha --recursive --exclude '**/helpers/*' --reporter ${REPORTER:-progress}", | ||
"test:cjs": "mocha cjs-test --recursive --exclude '**/helpers/*' --reporter ${REPORTER:-progress}", | ||
"test:dist": "mocha dist/test --reporter ${REPORTER:-progress}", | ||
@@ -42,2 +42,4 @@ "compile-modules": "node scripts/compile-modules.cjs", | ||
"watch": "node scripts/watch.cjs", | ||
"start": "discovery", | ||
"build-gh-pages": "discovery-build -o .gh-pages --clean --single-file", | ||
"prepare": "npm run compile-modules && npm run esm-to-cjs", | ||
@@ -47,2 +49,3 @@ "prepack": "npm run lint && npm run test && npm run test:cjs && npm run bundle-and-test", | ||
}, | ||
"discovery": "./docs/discovery/config.js", | ||
"dependencies": { | ||
@@ -52,6 +55,11 @@ "@discoveryjs/natural-compare": "^1.0.0" | ||
"devDependencies": { | ||
"@lahmatiy/jison": "^0.4.18-remastered.5", | ||
"@discoveryjs/cli": "^2.6.0", | ||
"@discoveryjs/discovery": "^1.0.0-beta.76", | ||
"@lahmatiy/jison": "^0.4.18-remastered.7", | ||
"c8": "^7.11.0", | ||
"esbuild": "~0.14.25", | ||
"eslint": "^8.10.0", | ||
"jora": ".", | ||
"js-beautify": "^1.14.8", | ||
"marked": "^5.1.0", | ||
"mocha": "^9.2.1", | ||
@@ -58,0 +66,0 @@ "rollup": "^2.70.0" |
405
README.md
@@ -10,5 +10,5 @@ # Jora | ||
JavaScript object query engine | ||
JavaScript object query language, and a library to process and perform Jora queries on data. | ||
> STATUS: Jora is still very much work in progress ([ideas and thoughts](https://gist.github.com/lahmatiy/d5af7a987e9548e80eae5f46e6edc931)). Syntax may change in next releases. | ||
> STATUS: Jora is stable, but syntax may change in next releases. Still very much work in progress ([ideas and thoughts](https://gist.github.com/lahmatiy/d5af7a987e9548e80eae5f46e6edc931)). | ||
@@ -35,29 +35,100 @@ Features: | ||
- [Install](#install) | ||
- [Quick demo](#quick-demo) | ||
- [API](#api) | ||
- [Query introspection](#query-introspection) | ||
- [Syntax](#syntax) | ||
- [Query syntax overview](#query-syntax-overview) | ||
- [Comments](#comments) | ||
- [Numbers](#numbers) | ||
- [Hexadecimal numbers](#hexadecimal-numbers) | ||
- [Strings](#strings) | ||
- [Regular expressions](#regular-expressions) | ||
- [Object literals](#object-literals) | ||
- [Array literals](#array-literals) | ||
- [Functions](#functions) | ||
- [Keywords](#keywords) | ||
- [Expressions](#expressions) | ||
- [Literals](#literals) | ||
- [Operators](#operators) | ||
- [Comparisons](#comparisons) | ||
- [Boolean logic](#boolean-logic) | ||
- [Block & definitions](#block--definitions) | ||
- [Special references](#special-references) | ||
- [Path chaining](#path-chaining) | ||
- [Build-in methods](#build-in-methods) | ||
- [License](#license) | ||
- [Dot, bracket and slice notations](#dot-bracket-and-slice-notations) | ||
- [Methods and functions](#methods-and-functions) | ||
- [Mapping and filtering](#mapping-and-filtering) | ||
- [Variables](#variables) | ||
- [NPM package](#npm-package) | ||
- [Install & import](#install--import) | ||
- [Quick demo](#quick-demo) | ||
- [API](#api) | ||
- [Query introspection](#query-introspection) | ||
<!-- /TOC --> | ||
## Install | ||
## Query syntax overview | ||
Jora is a query language designed for JSON-like data structures. It extends [JSON5](https://json5.org/) and shares many similarities with JavaScript. | ||
See [Docs & playground](https://discoveryjs.github.io/jora/). | ||
### Comments | ||
```js | ||
// single-line comment | ||
/* multi-line | ||
comment */ | ||
``` | ||
### Expressions | ||
Jora expressions are the building blocks of Jora queries. Expressions can include comments, literals, operators, functions, and variables. | ||
### Literals | ||
Jora supports literals, which include: | ||
- Numbers: `42`, `-3.14`, `6.022e23` | ||
- Strings: `"hello"`, `'world'`, `"\u{1F600}"` | ||
- Booleans: `true`, `false` | ||
- Regular expressions: `/regexp/flags` | ||
- Object literals: `{ hello: 'world' }` (see [Object literals](https://discoveryjs.github.io/jora/#article:jora-syntax-object-literal)) | ||
- Array literals: `[1, 2, 3]` (see [Array literals](https://discoveryjs.github.io/jora/#article:jora-syntax-array-literal)) | ||
- Functions: `=> …` (see [Functions](https://discoveryjs.github.io/jora/#article:functions)) | ||
- Keywords: `NaN`, `Infinity`, `null` and `undefined` | ||
See [Literals](https://discoveryjs.github.io/jora/#article:jora-syntax-literals) | ||
### Operators | ||
Jora supports most JavaScript operators, including: | ||
- Arithmetic: `+`, `-`, `*`, `/`, `%` | ||
- Comparison: `=`, `!=`, `<`, `<=`, `>`, `>=`, `~=` | ||
- Logical: `and`, `or`, `not` (alias `no`), `??`, `in`, `not in`, `has`, `has no` | ||
- Ternary: `?:` | ||
- Grouing: `( )` | ||
- Pipeline: `|` | ||
See [Operators](https://discoveryjs.github.io/jora/#article:jora-syntax-operators) | ||
### Dot, bracket and slice notations | ||
Jora provides notations for accessing properties and elements: dot, bracket and slice notations. Dot notation is similar to JavaScript's property access notation, using a period followed by the property name (e.g., `$.propertyName`). Bracket notation encloses the property name or index within square brackets (e.g., `$['propertyName']` or `$[0]`), it's also possible to use functions to choose. Slice notation provides a concise syntax to slice elements with optional step (`array[5:10:2]` selects each odd element from 5th to 10th indecies). | ||
- [Dot notation](https://discoveryjs.github.io/jora/#article:jora-syntax-dot-notation) | ||
- [Bracket notation](https://discoveryjs.github.io/jora/#article:jora-syntax-bracket-notation) | ||
- [Slice notation](https://discoveryjs.github.io/jora/#article:jora-syntax-slice-notation) | ||
### Methods and functions | ||
Jora provides a rich set of built-in methods for manipulating data, such as `map()`, `filter()`, `group()`, `sort()`, `reduce()`, and many others. You can also define custom functions using the `=>` arrow function syntax, and use them as a method. | ||
- [Methods](https://discoveryjs.github.io/jora/#article:jora-syntax-methods) | ||
- [Built-in methods](https://discoveryjs.github.io/jora/#article:jora-syntax-methods-builtin) | ||
- [Grouping](https://discoveryjs.github.io/jora/#article:jora-syntax-group): `group()` method | ||
- [Sorting](https://discoveryjs.github.io/jora/#article:jora-syntax-sort): `sort()` method | ||
### Mapping and filtering | ||
Jora has a concise syntax for mapping and filtering. The `map(fn)` method is equivalent to `.(fn())`, while the `filter(fn)` method is equivalent to `.[fn()]`. | ||
- [Mapping](https://discoveryjs.github.io/jora/#article:jora-syntax-map): `.(…)` and `map()` method | ||
- [Recursive mapping](https://discoveryjs.github.io/jora/#article:jora-syntax-recursive-map): `..(…)` | ||
- [Filtering](https://discoveryjs.github.io/jora/#article:jora-syntax-filter): `.[…]` and `filter()` method | ||
### Variables | ||
Variables in Jora are helpful for storing intermediate results or simplifying complex expressions. To define a variable, use the `$variableName: expression;` syntax. | ||
See [Variables](https://discoveryjs.github.io/jora/#article:jora-syntax-variables) | ||
## NPM package | ||
### Install & import | ||
Install with npm: | ||
@@ -93,22 +164,37 @@ | ||
import jora from 'node_modules/jora/dist/jora.esm.js' | ||
jora('query')(data, context); | ||
// ... | ||
</script> | ||
``` | ||
One of CDN services like `unpkg` or `jsDelivr` can be used. By default (for short path) a ESM version is exposing. For IIFE version a full path to a bundle should be specified: | ||
By default (for short path) a ESM version is exposing. For IIFE version a full path to a bundle should be specified. One of CDN services like `unpkg` or `jsDelivr` can be used: | ||
```html | ||
<!-- ESM --> | ||
<script type="module"> | ||
import jora from 'https://cdn.jsdelivr.net/npm/jora'; | ||
import jora from 'https://unpkg.com/jora'; | ||
</script> | ||
- `jsDeliver` | ||
<!-- IIFE with an export `jora` to global --> | ||
<script src="https://cdn.jsdelivr.net/npm/jora/dist/jora.js"></script> | ||
<script src="https://unpkg.com/jora/dist/jora.js"></script> | ||
``` | ||
```html | ||
<!-- ESM --> | ||
<script type="module"> | ||
import jora from 'https://cdn.jsdelivr.net/npm/jora'; | ||
</script> | ||
``` | ||
## Quick demo | ||
```html | ||
<!-- IIFE with an export `jora` to global --> | ||
<script src="https://cdn.jsdelivr.net/npm/jora/dist/jora.js"></script> | ||
``` | ||
- `unpkg` | ||
```html | ||
<!-- ESM --> | ||
<script type="module"> | ||
import jora from 'https://unpkg.com/jora'; | ||
</script> | ||
``` | ||
```html | ||
<!-- IIFE with an export `jora` to global --> | ||
<script src="https://unpkg.com/jora/dist/jora.js"></script> | ||
``` | ||
### Quick demo | ||
Get npm dependency paths (as a tree) that have packages with more than one version: | ||
@@ -176,3 +262,3 @@ | ||
## API | ||
### API | ||
@@ -223,2 +309,3 @@ ```js | ||
Enables stat mode. When mode is enabled a query stat interface is returning instead of resulting data. | ||
### Query introspection | ||
@@ -349,245 +436,1 @@ | ||
``` | ||
## Syntax | ||
### Comments | ||
``` | ||
// single-line comment | ||
/* multi-line | ||
comment */ | ||
``` | ||
### Numbers | ||
```js | ||
42 | ||
-123 | ||
4.22 | ||
1e3 | ||
1e-2 | ||
``` | ||
### Hexadecimal numbers | ||
```js | ||
0xdecaf | ||
-0xC0FFEE | ||
``` | ||
### Strings | ||
```js | ||
"string" | ||
'string' | ||
`template ${hello} ${world}` | ||
``` | ||
[Escape sequences](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#escape_sequences) are supported as well as an escaping to continue a string on next line: | ||
```js | ||
"\u2013 This is a very long string which needs \ | ||
to wrap across multiple lines because \ | ||
otherwise my code is unreadable\x21" | ||
``` | ||
### Regular expressions | ||
The same as in JavaScript. Supported flags: `i`, `g`, `m`, `s` and `u` | ||
```js | ||
/regexp/ | ||
/regexp/mi | ||
``` | ||
### Object literals | ||
Object initializer/literal syntax is the same as in JavaScript: | ||
```js | ||
{ foo: 123, bar: true } | ||
``` | ||
Spread operator (`...`) can be used in object literals as well, e.g. `{ a: 1, ..., ...foo }`. When spread operator used with no expression on the right side it's the same as `...$`. | ||
### Array literals | ||
Array initializer/literal syntax is the same as in JavaScript: | ||
```js | ||
[1, 'foo', { prop: 123 }] | ||
``` | ||
Spread operator (`...`) can be used, e.g. `[1, ...arr]`, but unlike JavaScript, spread operator in jora only inlines arrays and left as is any other values: | ||
```js | ||
[...[1, 2], ...3, ..."45", { "6": 7 }] // -> [1, 2, 3, "45", { "6": 7 }] | ||
``` | ||
When spread operator used with no expression on the right side it's the same as `...$`. | ||
### Functions | ||
```js | ||
=> expr | ||
``` | ||
> NOTE: The depricated syntax `< block >` is still supported, but avoid to use it since it will be removed in next releases. | ||
There are several ways to define a comparator function. Such functions (a sorting function) take two arguments and compare a query result for each in specified order (`asc` – ascending, `desc` – descending): | ||
```js | ||
expr asc // JS: (a, b) => expr(a) > expr(b) ? 1 : expr(a) < expr(b) ? -1 : 0 | ||
``` | ||
```js | ||
expr desc // JS: (a, b) => expr(a) < expr(b) ? 1 : expr(a) > expr(b) ? -1 : 0 | ||
``` | ||
A comma separated sequence defines a single function: | ||
```js | ||
foo asc, bar desc // JS: (a, b) => | ||
// a.foo > b.foo ? 1 : a.foo < b.foo ? -1 : | ||
// a.bar < b.bar ? 1 : a.bar > b.bar ? -1 : | ||
// 0 | ||
``` | ||
There are some modification for `asc` and `desc`: | ||
- `ascN` / `descN` – natural sorting (using [@discoveryjs/natural-compare](https://github.com/discoveryjs/natural-compare)) | ||
- `ascA` / `descA` – the same as `asc` / `desc` but reverse order for numbers | ||
- `ascAN` / `descAN` – the same as `asc`/`desc` but using natural compare and reverse order for numbers | ||
### Keywords | ||
Following keywords can be used with the same meaning as in JavaScript: | ||
- `true` | ||
- `false` | ||
- `null` | ||
- `undefined` | ||
- `Infinity` | ||
- `NaN` | ||
### Operators | ||
<table> | ||
<tr> | ||
<th>Jora | ||
<th>Description | ||
</tr> | ||
<tr> | ||
<td nowrap valign="top">x + y | ||
<td>Add<br>In case one of the operands is an array it produces new array with elements from `x` and `y` excluding duplicates | ||
</tr><tr> | ||
<td nowrap valign="top">x - y | ||
<td>Subtract<br>In case one of the operands is an array with elements from `x` excluding elements from `y` | ||
</tr><tr> | ||
<td nowrap>x * y | ||
<td>Multiply | ||
</tr><tr> | ||
<td nowrap>x / y | ||
<td>Divide | ||
</tr><tr> | ||
<td nowrap>x % y | ||
<td>Modulo | ||
</tr> | ||
</table> | ||
### Comparisons | ||
Jora | Description | ||
--- | --- | ||
x = y | Equals (as `===` in JS) | ||
x != y | Not equals (as `!==` in JS) | ||
x < y | Less than | ||
x <= y | Less than or equal to | ||
x > y | Greater than | ||
x >= y | Greater than or equal to | ||
x ~= y | Match operator, behaviour depends on `y` type:<br>RegExp – test against regexp<br>function – test like `filter()`<br>`null` or `undefined` – always truthy<br>anything else – always falsy | ||
### Boolean logic | ||
Jora | Description | ||
--- | --- | ||
( x ) | Explicity operator precedence. Definitions are allowed (i.e. `($a: 1; $a + $a)` see bellow) | ||
x or y | Boolean `or`.<br>Equivalent to `\|\|` in JS, but `x` tests with `bool()` method | ||
x and y | Boolean `and`.<br>Equivalent to `&&` in JS, but `x` tests with `bool()` method | ||
not x<br>no x | Boolean `not`.<br>Equivalent to `&&` in JS, but `x` tests with `bool()` method | ||
x ? y : z | If `x` is truthy than return `y` else return `z`. `x` tests with `bool()` method | ||
x in [a, b, c]<br>[a, b, c] has x | Equivalent to `x = a or x = b or x = c` | ||
x not in [a, b, c]<br>[a, b, c] has no x | Equivalent to `x != a and x != b and x != c` | ||
### Block & definitions | ||
Some constructions suppose to use a block, which may consists of a variable definition list (should comes first) and an expression. Both are optional. When an expression is empty, a current value (i.e. `$`) returns. | ||
The syntax of definition (white spaces between any part are optional): | ||
``` | ||
$ident ; | ||
$ident : expression ; | ||
``` | ||
For example: | ||
``` | ||
$foo:123; // Define `$foo` variable | ||
$bar; // The same as `$bar:$.bar;` or `$a: bar;` | ||
$baz: $foo + $bar; // Definitions may be used in following expressions | ||
``` | ||
In terms of JavaScript, a block creates a new scope. Once a variable is defined, its value never change. Variables can be redefined in nested scopes, but can't be duplicated in the same scope - it causes to error. | ||
### Special references | ||
Jora | Description | ||
--- | --- | ||
$ | A scope input data (current value). On top level scope it's the same as `@`. In most cases it may be omitted. Used implicitly an input for subquery when no other subjects is defined (e.g. `foo()` and `.foo()` are equivalent for `$.foo()`). | ||
$$ | A reference to the second parameter of closest function or undefined when no such | ||
@ | A query input data | ||
\# | A query context | ||
Since Jora's query performs as `query(data, context)`, in terms of Jora it looks like `query(@, #)`. | ||
### Path chaining | ||
jora | Description | ||
--- | --- | ||
ident | The same as `$.ident` | ||
.ident | Child member operator (example: `foo.bar.baz`, `#.foo['use any symbols for name']`) | ||
..ident<br>..( block ) | Recursive descendant operator (example: `..deps`, `..(deps + dependants)`) | ||
.[ block ] | Filter a current data. Equivalent to a `.filter(=>(block))` or `.filter(=>expr)` when a block has no definitions | ||
.( block ) | Map a current data. Equivalent to a `.map(=>(block))` or `.map(=>expr)` when a block has no definitions | ||
method()<br>.method()<br>..$method() | Invoke a method to current value, where `$method` is a reference to definition value (i.e. `$example: => $ * 10; 2.$plural(["example", "examples"])`). Can take arguments (i.e. `$method(one, 2)`). | ||
$method()<br>.$method()<br>..method() | Invoke a method to current value. See [build-in methods below](#build-in-methods) | ||
path[expr] | Array-like notation to access properties. Behaves like `pick()` method. In case you need to fetch a value to each element of array use `.($[expr])` or `map(=>$[expr])` | ||
[from:to]<br>[from:to:step] | [Slice notation](https://github.com/tc39/proposal-slice-notation/blob/master/README.md). Examples: `$str: '<foo>'; str[1:-1]` (result is `'foo'`) or `$ar:[1,2,3,4,5,6]; $ar[-3::-1]` (result is `[6,5,4]`) | ||
expr \| [definitions] expr \| ... | Pipeline operator. It's useful to make a query value as current value. Approximately this effect can be obtained using variables: `$ar: [1,2,3]; { size: $ar.size(), top2: $ar[0:2] }`. However, with pipeline operator it's a bit simplier and clear: `[1,2,3] | { size: size(), top2: [0:2] }` | ||
### Build-in methods | ||
jora | Description | ||
--- | --- | ||
bool() | The same as `Boolean()` in JS, with exception that *empty arrays* and *objects with no keys* treats as falsy | ||
keys() | The same as `Object.keys()` in JS | ||
values() | The same as `Object.values()` in JS | ||
entries() | Similar to `Object.entries()` in JS with a difference: `{ key, value }` objects is using for entries instead of array tuples | ||
fromEntries() | Similar to `Object.fromEntries()` in JS with difference: `{ key, value }` objects are expecting as entries instead of array tuples | ||
pick("key")<br>pick(index)<br>pick(fn) | Get a value by a key, an index or a function. It returns an element with `e` index for arrays, a char with `e` index for strings, and a value with `e` key (must be own key) for enything else. Negative indecies are supported for arrays and strings. Current value is element for an array, a char for a string or an entry value for object. Arg1 (i.e. `$$`) is an index for arrays and strings, and a key for objects. | ||
size() | Returns count of keys if current data is object, otherwise returns `length` value or `0` when field is absent | ||
sort(fn) | Sort an array by a value fetched with getter (`<fn>`). Keep in mind, you can use sorting function definition syntax using `asc` and `desc` keywords, qhich is more effective in many ways. In case of sorting function definition usage, `<` and `>` are not needed and you can specify sorting order for each component. The following queries are equivalent:<br>`sort(=> foo.bar)` and `sort(foo.bar asc)`<br>`sort(=> foo).reverse()` and `sort(foo desc)`<br>`sort(=> [a, b])` and `sort(a asc, b asc)` | ||
reverse() | Reverse order of items | ||
group(fn[, fn]) | Group an array items by a value fetched with first getter and return an array of `{ key, value }` entries. The second parameter is used to fetch a value, the following queries are equivalent:<br>`group(=> foo, => bar)` and `group(=> foo).({ key, value: value.bar })` | ||
filter(fn) | The same as `Array#filter()` in JS | ||
map(fn) | The same as `Array#map()` in JS | ||
split(pattern) | The same as `String#split()` in JS. `pattern` may be a string or regexp | ||
join(separator) | The same as `Array#join()` in JS. When `separator` is undefined then `","` is using | ||
slice(from, to) | The same as `Array#slice()` or `String#slice()` in JS | ||
match(pattern, matchAll?) | Similar to `String#match()`. `pattern` might be a RegExp or string. When `matchAll` is truthy then returns an array of all occurrences of the `pattern`. Expressions `match(/../g)` and `match(/../, true)` are equivalent. | ||
reduce(fn\[, initValue]) | The same as `Array#reduce()` in JS. Use `$$` to access to accumulator and `$` to current value, e.g. find the max value `reduce(=>$ > $$ ? $ : $$)` | ||
## License | ||
MIT |
import { version } from './version.js'; | ||
import { hasOwn } from './utils/misc.js'; | ||
import parser from './lang/parse.js'; | ||
@@ -9,2 +10,3 @@ import suggest from './lang/suggest.js'; | ||
import methods from './methods.js'; | ||
import assertions from './assertions.js'; | ||
import createStatApi from './stat.js'; | ||
@@ -17,2 +19,48 @@ | ||
function defineDictFunction(dict, name, fn, queryMethods, queryAssertions) { | ||
if (typeof fn === 'string') { | ||
Object.defineProperty(dict, name, { | ||
configurable: true, | ||
get() { | ||
const compiledFn = compileFunction(fn)(buildin, queryMethods, queryAssertions); | ||
const value = current => compiledFn(current, null); | ||
Object.defineProperty(dict, name, { value }); | ||
return value; | ||
} | ||
}); | ||
} else { | ||
dict[name] = fn; | ||
} | ||
} | ||
function buildQueryMethodsAndAssertions(customMethods, customAssertions) { | ||
if (!customMethods && !customAssertions) { | ||
return { | ||
queryMethods: methods, | ||
queryAssertions: assertions | ||
}; | ||
} | ||
const queryMethods = { ...methods }; | ||
const queryAssertions = { ...assertions }; | ||
for (const [name, fn] of Object.entries(customMethods || {})) { | ||
if (hasOwn(methods, name)) { | ||
throw new Error(`Builtin method "${name}" can\'t be overridden`); | ||
} | ||
defineDictFunction(queryMethods, name, fn, queryMethods, queryAssertions); | ||
} | ||
for (const [name, fn] of Object.entries(customAssertions || {})) { | ||
if (hasOwn(assertions, name)) { | ||
throw new Error(`Builtin assertion "${name}" can\'t be overridden`); | ||
} | ||
defineDictFunction(queryAssertions, name, fn, queryMethods, queryAssertions); | ||
} | ||
return { queryMethods, queryAssertions }; | ||
} | ||
function defaultDebugHandler(sectionName, value) { | ||
@@ -93,6 +141,8 @@ console.log(`[${sectionName}]`); | ||
const tolerantMode = Boolean(options.tolerant); | ||
const localMethods = options.methods ? { ...methods, ...options.methods } : methods; | ||
const cache = statMode | ||
? (tolerantMode ? cacheTollerantStat : cacheStrictStat) | ||
: (tolerantMode ? cacheTollerant : cacheStrict); | ||
const { methods: customMethods, assertions: customAssertions } = options || {}; | ||
const { queryMethods, queryAssertions } = | ||
buildQueryMethodsAndAssertions(customMethods, customAssertions); | ||
let fn; | ||
@@ -109,10 +159,10 @@ | ||
fn = fn(buildin, localMethods); | ||
fn = fn(buildin, queryMethods, queryAssertions); | ||
return statMode | ||
? (data, context) => createStatApi(source, fn(data, context)) | ||
? Object.assign((data, context) => createStatApi(source, fn(data, context)), { query: fn }) | ||
: fn; | ||
} | ||
function setup(customMethods) { | ||
function setup(options) { | ||
const cacheStrict = new Map(); | ||
@@ -122,20 +172,6 @@ const cacheStrictStat = new Map(); | ||
const cacheTollerantStat = new Map(); | ||
const localMethods = { ...methods }; | ||
const { methods: customMethods, assertions: customAssertions } = options || {}; | ||
const { queryMethods, queryAssertions } = | ||
buildQueryMethodsAndAssertions(customMethods, customAssertions); | ||
for (const [name, fn] of Object.entries(customMethods || {})) { | ||
if (typeof fn === 'string') { | ||
Object.defineProperty(localMethods, name, { | ||
configurable: true, | ||
get() { | ||
const compiledFn = compileFunction(fn)(buildin, localMethods); | ||
const value = current => compiledFn(current, null); | ||
Object.defineProperty(localMethods, name, { value }); | ||
return value; | ||
} | ||
}); | ||
} else { | ||
localMethods[name] = fn; | ||
} | ||
} | ||
return function query(source, options) { | ||
@@ -156,5 +192,14 @@ options = options || {}; | ||
} else { | ||
const perform = compileFunction(source, statMode, tolerantMode, options.debug)(buildin, localMethods); | ||
const perform = compileFunction( | ||
source, | ||
statMode, | ||
tolerantMode, | ||
options.debug | ||
)( | ||
buildin, | ||
queryMethods, | ||
queryAssertions | ||
); | ||
fn = statMode | ||
? (data, context) => createStatApi(source, perform(data, context)) | ||
? Object.assign((data, context) => createStatApi(source, perform(data, context)), { query: perform }) | ||
: perform; | ||
@@ -172,2 +217,3 @@ cache.set(source, fn); | ||
methods, | ||
assertions, | ||
setup, | ||
@@ -174,0 +220,0 @@ syntax: { |
@@ -12,2 +12,9 @@ export function Arg1() { | ||
}; | ||
export function Assertion(assertion, negation = false) { | ||
return { | ||
type: 'Assertion', | ||
negation, | ||
assertion | ||
}; | ||
}; | ||
export function Binary(operator, left, right) { | ||
@@ -35,2 +42,8 @@ return { | ||
}; | ||
export function CompareFunction(compares) { | ||
return { | ||
type: 'CompareFunction', | ||
compares | ||
}; | ||
}; | ||
export function Conditional(test, consequent, alternate) { | ||
@@ -172,2 +185,16 @@ return { | ||
}; | ||
export function Postfix(argument, operator) { | ||
return { | ||
type: 'Postfix', | ||
operator, | ||
argument | ||
}; | ||
}; | ||
export function Prefix(operator, argument) { | ||
return { | ||
type: 'Prefix', | ||
operator, | ||
argument | ||
}; | ||
}; | ||
export function Reference(name) { | ||
@@ -186,8 +213,2 @@ return { | ||
}; | ||
export function SortingFunction(compares) { | ||
return { | ||
type: 'SortingFunction', | ||
compares | ||
}; | ||
}; | ||
export function Spread(query, array = false) { | ||
@@ -206,8 +227,1 @@ return { | ||
}; | ||
export function Unary(operator, argument) { | ||
return { | ||
type: 'Unary', | ||
operator, | ||
argument | ||
}; | ||
}; |
@@ -1,35 +0,9 @@ | ||
import { naturalCompare, naturalAnalyticalCompare } from '@discoveryjs/natural-compare'; | ||
import { hasOwnProperty, addToSet, getPropertyValue, isPlainObject, isRegExp, isArrayLike } from '../utils.js'; | ||
import { cmp, cmpAnalytical, cmpNatural, cmpNaturalAnalytical } from '../utils/compare.js'; | ||
import { hasOwn, addToSet, getPropertyValue, isPlainObject, isRegExp, isArrayLike, isTruthy } from '../utils/misc.js'; | ||
const TYPE_BOOLEAN = 1; | ||
const TYPE_NAN = 2; | ||
const TYPE_NUMBER = 3; | ||
const TYPE_STRING = 4; | ||
const TYPE_NULL = 5; | ||
const TYPE_OBJECT = 6; | ||
const TYPE_OTHER = 7; | ||
const TYPE_UNDEFINED = 8; | ||
function cmpType(value) { | ||
switch (typeof value) { | ||
case 'boolean': | ||
return TYPE_BOOLEAN; | ||
case 'number': | ||
return value !== value ? /* NaN */ TYPE_NAN : TYPE_NUMBER; | ||
case 'string': | ||
return TYPE_STRING; | ||
case 'object': | ||
return value === null ? TYPE_NULL : TYPE_OBJECT; | ||
case 'undefined': | ||
return TYPE_UNDEFINED; | ||
default: | ||
return TYPE_OTHER; | ||
} | ||
} | ||
export default Object.freeze({ | ||
ensureArray, | ||
bool, | ||
and: (a, b) => bool(a) ? b : a, | ||
or: (a, b) => bool(a) ? a : b, | ||
bool: isTruthy, | ||
and: (a, b) => isTruthy(a) ? b : a, | ||
or: (a, b) => isTruthy(a) ? a : b, | ||
add, | ||
@@ -56,2 +30,4 @@ sub, | ||
pick, | ||
indexOf, | ||
lastIndexOf, | ||
map, | ||
@@ -68,20 +44,2 @@ mapRecursive, | ||
function bool(value) { | ||
if (Array.isArray(value)) { | ||
return value.length > 0; | ||
} | ||
if (isPlainObject(value)) { | ||
for (const key in value) { | ||
if (hasOwnProperty.call(value, key)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
return Boolean(value); | ||
} | ||
function add(a, b) { | ||
@@ -150,60 +108,10 @@ if (Array.isArray(a) || Array.isArray(b)) { | ||
if (isPlainObject(b)) { | ||
return hasOwnProperty.call(b, a); | ||
return hasOwn(b, a); | ||
} | ||
return b && typeof b.indexOf === 'function' ? b.indexOf(a) !== -1 : false; | ||
return b | ||
? internalIndexOf(b, a) !== -1 | ||
: false; | ||
} | ||
function cmp(a, b) { | ||
const typeA = cmpType(a); | ||
const typeB = cmpType(b); | ||
return typeA !== typeB | ||
? (typeA < typeB ? -1 : 1) | ||
: (a < b ? -1 : a > b ? 1 : 0); | ||
} | ||
function cmpAnalytical(a, b) { | ||
const typeA = cmpType(a); | ||
const typeB = cmpType(b); | ||
if (typeA !== typeB) { | ||
return typeA < typeB ? -1 : 1; | ||
} | ||
if (typeA === TYPE_NUMBER) { | ||
return b - a; // reverse order for numbers | ||
} | ||
return a < b ? -1 : a > b ? 1 : 0; | ||
} | ||
function cmpNatural(a, b) { | ||
const typeA = cmpType(a); | ||
const typeB = cmpType(b); | ||
if ((typeA === TYPE_NUMBER || typeA === TYPE_STRING) && | ||
(typeB === TYPE_NUMBER || typeB === TYPE_STRING)) { | ||
return naturalCompare(a, b); | ||
} | ||
return typeA !== typeB | ||
? (typeA < typeB ? -1 : 1) | ||
: (a < b ? -1 : a > b ? 1 : 0); | ||
} | ||
function cmpNaturalAnalytical(a, b) { | ||
const typeA = cmpType(a); | ||
const typeB = cmpType(b); | ||
if ((typeA === TYPE_NUMBER || typeA === TYPE_STRING) && | ||
(typeB === TYPE_NUMBER || typeB === TYPE_STRING)) { | ||
return naturalAnalyticalCompare(a, b, true); | ||
} | ||
return typeA !== typeB | ||
? (typeA < typeB ? -1 : 1) | ||
: (a < b ? -1 : a > b ? 1 : 0); | ||
} | ||
function match(value, pattern) { | ||
@@ -240,3 +148,3 @@ if (typeof pattern === 'function') { | ||
for (const key in current) { | ||
if (hasOwnProperty.call(current, key)) { | ||
if (hasOwn(current, key)) { | ||
if (ref(current[key], key)) { | ||
@@ -257,5 +165,53 @@ return current[key]; | ||
return hasOwnProperty.call(current, ref) ? current[ref] : undefined; | ||
return hasOwn(current, ref) ? current[ref] : undefined; | ||
} | ||
function indexOf(dict, value, fromIndex) { | ||
return dict | ||
? internalIndexOf(dict, value, fromIndex) | ||
: -1; | ||
} | ||
function internalIndexOf(dict, value, fromIndex = 0) { | ||
if (Number.isNaN(value)) { | ||
if (isArrayLike(dict)) { | ||
for (let i = parseInt(fromIndex, 10) || 0; i < dict.length; i++) { | ||
if (Number.isNaN(dict[i])) { | ||
return i; | ||
} | ||
} | ||
} | ||
} | ||
if (typeof dict.indexOf === 'function') { | ||
return dict.indexOf(value, fromIndex); | ||
} | ||
return -1; | ||
} | ||
function lastIndexOf(dict, value, fromIndex) { | ||
return dict | ||
? internalLastIndexOf(dict, value, fromIndex) | ||
: -1; | ||
} | ||
function internalLastIndexOf(dict, value, fromIndex) { | ||
if (Number.isNaN(value)) { | ||
if (isArrayLike(dict)) { | ||
for (let i = parseInt(fromIndex, 10) || dict.length - 1; i >= 0; i--) { | ||
if (Number.isNaN(dict[i])) { | ||
return i; | ||
} | ||
} | ||
} | ||
} | ||
if (typeof dict.lastIndexOf === 'function') { | ||
return dict.lastIndexOf(value, parseInt(fromIndex, 10) || dict.length - 1); | ||
} | ||
return -1; | ||
} | ||
function map(value, getter) { | ||
@@ -292,4 +248,4 @@ const fn = typeof getter === 'function' | ||
return Array.isArray(value) | ||
? value.some(current => bool(fn(current))) | ||
: bool(fn(value)); | ||
? value.some(current => isTruthy(fn(current))) | ||
: isTruthy(fn(value)); | ||
} | ||
@@ -299,6 +255,6 @@ | ||
if (Array.isArray(value)) { | ||
return value.filter(current => bool(fn(current))); | ||
return value.filter(current => isTruthy(fn(current))); | ||
} | ||
return bool(fn(value)) ? value : undefined; | ||
return isTruthy(fn(value)) ? value : undefined; | ||
} | ||
@@ -305,0 +261,0 @@ |
@@ -1,2 +0,2 @@ | ||
import { hasOwnProperty } from '../utils.js'; | ||
import { hasOwn } from '../utils/misc.js'; | ||
import createError from './error.js'; | ||
@@ -148,3 +148,3 @@ import { compile as nodes } from './nodes/index.js'; | ||
} }, | ||
'return ' | ||
suggestions === null ? 'return ' : 'return{\nvalue: ' | ||
]; | ||
@@ -156,2 +156,3 @@ | ||
tolerant, | ||
usedAssertions: new Map(), | ||
usedMethods: new Map(), | ||
@@ -209,26 +210,50 @@ buildinFn(name) { | ||
if (!tolerant && ctx.usedMethods.size) { | ||
const { usedMethods } = ctx; | ||
if (!tolerant) { | ||
const { usedMethods, usedAssertions } = ctx; | ||
buffer.unshift(' this.assertMethods(m)||'); | ||
initCtx.assertMethods = function(providedMethods) { | ||
for (const [method, range] of usedMethods.entries()) { | ||
if (!hasOwnProperty.call(providedMethods, method)) { | ||
return () => { | ||
throw Object.assign( | ||
new Error(`Method "${method}" is not defined`), | ||
{ details: { loc: { range } } } | ||
); | ||
}; | ||
if (usedAssertions.size) { | ||
buffer.unshift(' this.assertAssertions(a)||'); | ||
initCtx.assertAssertions = function(providedAssertions) { | ||
for (const [assertion, range] of usedAssertions.entries()) { | ||
if (!hasOwn(providedAssertions, assertion)) { | ||
return () => { | ||
throw Object.assign( | ||
new Error(`Assertion "${assertion}" is not defined`), | ||
{ details: { loc: { range } } } | ||
); | ||
}; | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
if (usedMethods.size) { | ||
buffer.unshift(' this.assertMethods(m)||'); | ||
initCtx.assertMethods = function(providedMethods) { | ||
for (const [method, range] of usedMethods.entries()) { | ||
if (!hasOwn(providedMethods, method)) { | ||
return () => { | ||
throw Object.assign( | ||
new Error( | ||
`Method "${method}" is not defined. If that's a custom method ` + | ||
'make sure you added it with "methods" section in options' | ||
), | ||
{ details: { loc: { range } } } | ||
); | ||
}; | ||
} | ||
} | ||
}; | ||
} | ||
} | ||
if (suggestions !== null) { | ||
buffer.push('\n,[' + normalizedSuggestRanges.map(s => '[' + s + ']') + ']'); | ||
buffer.push( | ||
',\nstats: [' + normalizedSuggestRanges.map(s => '[' + s + ']') + ']' + | ||
',\nassertions: a' + | ||
'\n}'); | ||
} | ||
try { | ||
const fn = new Function('f,m', 'return' + buffer.join('') + '})'); | ||
const fn = new Function('f,m,a', 'return' + buffer.join('') + '})'); | ||
@@ -235,0 +260,0 @@ return Object.assign(fn.bind(initCtx), { |
@@ -8,2 +8,3 @@ const binary = { | ||
'or': 'or', | ||
'??': 'nullish', | ||
'+': 'add', | ||
@@ -81,3 +82,3 @@ '-': 'sub', | ||
switch (node.operator) { | ||
// separate branch since node.right might not to be evaluated when: | ||
// separate branch since node.right might not to be evaluated (short-circuiting) when: | ||
// - node.left is falsy for "and" | ||
@@ -99,2 +100,16 @@ // - node.left is truthy for "or" | ||
// separate branch since node.right might not to be evaluated (short-circuiting) | ||
// when node.left is null or undefined | ||
case '??': { | ||
const tmpVar = ctx.allocateVar(); | ||
ctx.put(`(${tmpVar}=`); | ||
ctx.node(node.left); | ||
ctx.put(`,${tmpVar}!==null&&${tmpVar}!==undefined)?${tmpVar}:`); | ||
ctx.scope.captureCurrent.disabled = true; | ||
ctx.node(node.right); | ||
ctx.scope.captureCurrent.disabled = false; | ||
break; | ||
} | ||
// separate branch since suggest should collect stat for node.right first | ||
@@ -101,0 +116,0 @@ case 'has no': |
export function compile(node, ctx) { | ||
ctx.put(ctx.buildinFn('bool')); | ||
ctx.put('('); | ||
ctx.node(node.test); | ||
ctx.scope.captureCurrent.disabled = true; | ||
ctx.nodeOrCurrent(node.test); | ||
ctx.put(')?'); | ||
ctx.node(node.consequent); | ||
ctx.nodeOrCurrent(node.consequent); | ||
ctx.put(':'); | ||
ctx.node(node.alternate); | ||
if (node.alternate) { | ||
ctx.node(node.alternate); | ||
} else { | ||
ctx.put('undefined'); | ||
} | ||
ctx.scope.captureCurrent.disabled = false; | ||
} | ||
export function walk(node, ctx) { | ||
ctx.node(node.test); | ||
ctx.node(node.consequent); | ||
ctx.node(node.alternate); | ||
ctx.nodeOrNothing(node.test); | ||
ctx.nodeOrNothing(node.consequent); | ||
ctx.nodeOrNothing(node.alternate); | ||
} | ||
export function stringify(node, ctx) { | ||
ctx.node(node.test); | ||
ctx.nodeOrNothing(node.test); | ||
ctx.put('?'); | ||
ctx.node(node.consequent); | ||
ctx.put(':'); | ||
ctx.node(node.alternate); | ||
ctx.nodeOrNothing(node.consequent); | ||
if (node.alternate) { | ||
ctx.put(':'); | ||
ctx.node(node.alternate); | ||
} | ||
} |
import * as Arg1 from './Arg1.js'; | ||
import * as Array from './Array.js'; | ||
import * as Assertion from './Assertion.js'; | ||
import * as Binary from './Binary.js'; | ||
import * as Block from './Block.js'; | ||
import * as Compare from './Compare.js'; | ||
import * as CompareFunction from './CompareFunction.js'; | ||
import * as Conditional from './Conditional.js'; | ||
@@ -27,8 +29,8 @@ import * as Context from './Context.js'; | ||
import * as Placeholder from './Placeholder.js'; | ||
import * as Postfix from './Postfix.js'; | ||
import * as Prefix from './Prefix.js'; | ||
import * as Reference from './Reference.js'; | ||
import * as SliceNotation from './SliceNotation.js'; | ||
import * as SortingFunction from './SortingFunction.js'; | ||
import * as Spread from './Spread.js'; | ||
import * as Template from './Template.js'; | ||
import * as Unary from './Unary.js'; | ||
@@ -38,2 +40,3 @@ export const nodes = { | ||
Array, | ||
Assertion, | ||
Binary, | ||
@@ -63,8 +66,9 @@ Block, | ||
Placeholder, | ||
Postfix, | ||
Prefix, | ||
Reference, | ||
SliceNotation, | ||
SortingFunction, | ||
CompareFunction, | ||
Spread, | ||
Template, | ||
Unary | ||
Template | ||
}; | ||
@@ -71,0 +75,0 @@ |
@@ -9,3 +9,3 @@ export function compile(node, ctx) { | ||
(scopeStart, sp) => { | ||
return scopeStart + sp + ';'; | ||
return scopeStart + sp + ','; | ||
} | ||
@@ -12,0 +12,0 @@ ); |
import buildin from './lang/compile-buildin.js'; | ||
import { hasOwnProperty, addToSet, isPlainObject, isRegExp } from './utils.js'; | ||
import { cmp } from './utils/compare.js'; | ||
import { numbers, count, sum, mean, variance, stdev, min, max, percentile, median } from './utils/statistics.js'; | ||
import { hasOwn, addToSet, addToMapSet, isPlainObject, isRegExp } from './utils/misc.js'; | ||
@@ -11,2 +13,6 @@ function noop() {} | ||
function matchEntry(match) { | ||
if (match === null) { | ||
return null; | ||
} | ||
return { | ||
@@ -21,2 +27,23 @@ matched: match.slice(), | ||
function replaceMatchEntry(args) { | ||
const last = args.pop(); | ||
const groups = typeof last === 'string' ? null : last; | ||
const input = groups === null ? last : args.pop(); | ||
const start = args.pop(); | ||
return { | ||
matched: args, | ||
start, | ||
end: start + args[0].length, | ||
input, | ||
groups | ||
}; | ||
} | ||
const replaceAll = String.prototype.replaceAll || function(pattern, replacement) { | ||
return isRegExp(pattern) | ||
? this.replace(pattern, replacement) | ||
: this.split(pattern).join(String(replacement)); | ||
}; | ||
const stableSortSize = isSortStable(20) ? Infinity : isSortStable(10) ? 10 : 0; | ||
@@ -55,2 +82,4 @@ | ||
pick: buildin.pick, | ||
indexOf: buildin.indexOf, | ||
lastIndexOf: buildin.lastIndexOf, | ||
keys(current) { | ||
@@ -63,3 +92,3 @@ return Object.keys(current || {}); | ||
for (const key in current) { | ||
if (hasOwnProperty.call(current, key)) { | ||
if (hasOwn(current, key)) { | ||
addToSet(values, current[key]); | ||
@@ -75,3 +104,3 @@ } | ||
for (const key in current) { | ||
if (hasOwnProperty.call(current, key)) { | ||
if (hasOwn(current, key)) { | ||
entries.push({ key, value: current[key] }); | ||
@@ -84,10 +113,10 @@ } | ||
fromEntries(current) { | ||
const result = {}; | ||
const result = Object.create(null); | ||
if (Array.isArray(current)) { | ||
current.forEach(entry => { | ||
for (const entry of current) { | ||
if (entry) { | ||
result[entry.key] = entry.value; | ||
} | ||
}); | ||
} | ||
} | ||
@@ -104,5 +133,3 @@ | ||
}, | ||
sort(current, fn) { | ||
let sorter; | ||
sort(current, comparator = cmp) { | ||
if (!Array.isArray(current)) { | ||
@@ -112,7 +139,9 @@ return current; | ||
if (typeof fn === 'function') { | ||
sorter = fn.length === 2 ? fn : (a, b) => { | ||
a = fn(a); | ||
b = fn(b); | ||
if (typeof comparator === 'function' && comparator.length !== 2) { | ||
const getter = comparator; | ||
comparator = (a, b) => { | ||
a = getter(a); | ||
b = getter(b); | ||
if (Array.isArray(a) && Array.isArray(b)) { | ||
@@ -124,6 +153,6 @@ if (a.length !== b.length) { | ||
for (let i = 0; i < a.length; i++) { | ||
if (a[i] < b[i]) { | ||
return -1; | ||
} else if (a[i] > b[i]) { | ||
return 1; | ||
const ret = cmp(a[i], b[i]); | ||
if (ret !== 0) { | ||
return ret; | ||
} | ||
@@ -135,16 +164,12 @@ } | ||
return a < b ? -1 : a > b; | ||
return cmp(a, b); | ||
}; | ||
} else { | ||
sorter = buildin.cmp; | ||
} | ||
return stableSort(current, sorter); | ||
return stableSort(current, comparator); | ||
}, | ||
reverse(current) { | ||
if (!Array.isArray(current)) { | ||
return current; | ||
} | ||
return current.slice().reverse(); | ||
return Array.isArray(current) | ||
? current.slice().reverse() | ||
: current; | ||
}, | ||
@@ -155,2 +180,5 @@ slice(current, from, to) { | ||
group(current, keyGetter, valueGetter) { | ||
const map = new Map(); | ||
const result = []; | ||
if (typeof keyGetter !== 'function') { | ||
@@ -168,30 +196,20 @@ keyGetter = noop; | ||
const map = new Map(); | ||
const result = []; | ||
for (const item of current) { | ||
const keys = keyGetter(item); | ||
current.forEach(item => { | ||
let keys = keyGetter(item); | ||
if (!Array.isArray(keys)) { | ||
keys = [keys]; | ||
if (Array.isArray(keys)) { | ||
for (const key of keys) { | ||
addToMapSet(map, key, valueGetter(item)); | ||
} | ||
} else { | ||
addToMapSet(map, keys, valueGetter(item)); | ||
} | ||
} | ||
keys.forEach(key => { | ||
if (map.has(key)) { | ||
map.get(key).add(valueGetter(item)); | ||
} else { | ||
map.set(key, new Set([valueGetter(item)])); | ||
} | ||
}); | ||
}); | ||
for (const [key, value] of map) { | ||
result.push({ key, value: [...value] }); | ||
} | ||
map.forEach((value, key) => | ||
result.push({ key, value: [...value] }) | ||
); | ||
return result; | ||
}, | ||
split(current, pattern) { | ||
return String(current).split(pattern); | ||
}, | ||
join(current, separator) { | ||
@@ -218,4 +236,3 @@ return Array.isArray(current) | ||
const match = input.match(pattern); | ||
return match && matchEntry(match); | ||
return matchEntry(input.match(pattern)); | ||
}, | ||
@@ -230,3 +247,86 @@ reduce(current, fn, initValue = undefined) { | ||
return fn(current, initValue); | ||
} | ||
}, | ||
// array/string | ||
split(current, pattern) { | ||
if (Array.isArray(current)) { | ||
const patternFn = typeof pattern === 'function' ? pattern : Object.is.bind(null, pattern); | ||
const result = []; | ||
let start = 0; | ||
let end = 0; | ||
for (; end < current.length; end++) { | ||
if (patternFn(current[end])) { | ||
result.push(current.slice(start, end)); | ||
start = end + 1; | ||
} | ||
} | ||
result.push(current.slice(start, end)); | ||
return result; | ||
} | ||
return String(current).split(pattern); | ||
}, | ||
replace(current, pattern, replacement) { | ||
if (Array.isArray(current)) { | ||
const patternFn = typeof pattern === 'function' ? pattern : Object.is.bind(null, pattern); | ||
return current.map( | ||
typeof replacement === 'function' | ||
? current => patternFn(current) ? replacement(current) : current | ||
: current => patternFn(current) ? replacement : current | ||
); | ||
} | ||
if (isRegExp(pattern) && !pattern.flags.includes('g')) { | ||
pattern = new RegExp(pattern, pattern.flags + 'g'); | ||
} | ||
return replaceAll.call( | ||
String(current), | ||
pattern, | ||
typeof replacement === 'function' | ||
? (...args) => replacement(replaceMatchEntry(args)) | ||
: replacement | ||
); | ||
}, | ||
// strings | ||
toLowerCase(current, locales) { | ||
return String(current).toLocaleLowerCase(locales); | ||
}, | ||
toUpperCase(current, locales) { | ||
return String(current).toLocaleUpperCase(locales); | ||
}, | ||
trim(current) { | ||
return String(current).trim(); | ||
}, | ||
// all Math static method with exclusion of 'max', 'min', 'log', `log1p` and 'random' | ||
...[ | ||
'abs', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', | ||
'cbrt', 'ceil', 'clz32', 'cos', 'cosh', 'exp', 'expm1', 'floor', | ||
'fround', 'hypot', 'imul', 'log10', 'log2', 'pow', | ||
'round', 'sign', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc' | ||
].reduce((res, method) => { | ||
res[method] = Math[method]; | ||
return res; | ||
}, {}), | ||
ln: Math.log, | ||
ln1p: Math.log1p, | ||
// statistics | ||
numbers, | ||
count, | ||
sum, | ||
avg: mean, | ||
variance, | ||
stdev, | ||
min, | ||
max, | ||
percentile, | ||
p: percentile, // alias for percentile() | ||
median | ||
}); |
@@ -1,4 +0,4 @@ | ||
import { MaxHeap } from './max-heap.js'; | ||
import { isPlainObject } from './utils.js'; | ||
import buildin from './lang/compile-buildin.js'; | ||
import { MaxHeap } from './utils/heap.js'; | ||
import { isPlainObject } from './utils/misc.js'; | ||
@@ -11,3 +11,4 @@ const contextToType = { | ||
'value-subset': 'value', | ||
'var': 'variable' | ||
'var': 'variable', | ||
'assertion': 'assertion' | ||
}; | ||
@@ -112,3 +113,3 @@ | ||
function findSourcePosRanges(source, pos, points, includeEmpty = false) { | ||
const result = []; | ||
const ranges = []; | ||
@@ -124,3 +125,3 @@ for (let [from, to, context, values, related = null] of points) { | ||
result.push({ | ||
ranges.push({ | ||
context, | ||
@@ -136,3 +137,3 @@ from, | ||
return result; | ||
return ranges; | ||
} | ||
@@ -180,5 +181,8 @@ | ||
export default (source, points) => ({ | ||
export default (source, { value, stats, assertions }) => ({ | ||
get value() { | ||
return value; | ||
}, | ||
stat(pos, includeEmpty) { | ||
return findSourcePosRanges(source, pos, points, includeEmpty); | ||
return findSourcePosRanges(source, pos, stats, includeEmpty); | ||
}, | ||
@@ -191,3 +195,3 @@ suggestion(pos, options) { | ||
const storageType = sort && isFinite(limit) ? MaxHeap : Set; | ||
const ranges = findSourcePosRanges(source, pos, points); | ||
const ranges = findSourcePosRanges(source, pos, stats, true); | ||
const typeSuggestions = new Map(); | ||
@@ -227,3 +231,15 @@ const result = []; | ||
const { suggestions } = typeSuggestions.get(type); | ||
valuesToSuggestions(context, values, related, suggestions); | ||
switch (context) { | ||
case 'assertion': | ||
if (suggestions.size === 0 || (suggestions.values && suggestions.values.length === 0)) { | ||
for (const value of Object.keys(assertions)) { | ||
suggestions.add(value); | ||
} | ||
} | ||
break; | ||
default: | ||
valuesToSuggestions(context, values, related, suggestions); | ||
} | ||
} | ||
@@ -230,0 +246,0 @@ |
@@ -1,1 +0,1 @@ | ||
export const version = '1.0.0-beta.7'; | ||
export const version = '1.0.0-beta.8'; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
119
10289
1030866
11
431