Skip to content

Commit

Permalink
Forbid to override built-in methods, and related refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
lahmatiy committed Sep 21, 2023
1 parent 2d912d0 commit d44456d
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 201 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
## next

- Reduced parser size by 15Kb
- Changed `setup()` function to take `options` parameter instead of custom methods dictionary, i.e. `setup(methods)``setup({ methods })`
- Added `assertions` option for `jora()` and `setup()` functions to specify additional assertion functions, i.e. `jora(..., { assetions })` and `setup({ assertions })`
- Forbidden to override built-in methods and assertions, now `setup()` and `query()` functions throws when a custom method or assertion has the same name as built-in one
- Extended query result object in stat mode to provide a result value of the query execution as `value` property (i.e. `jora(query, { stat: true })().value`)
- Added `assertions` option for `jora()` and second argument for `setup()` method to specify additional assertion functions
- Renamed `SortingFunction` AST node type into `CompareFunction`
- Renamed `Unary` AST node type into `Prefix`
- Added `Assertion` and `Postfix` AST node types
Expand Down
1 change: 0 additions & 1 deletion docs/discovery/prepare.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ module.exports = function(data, { addQueryHelpers, defineObjectMarker }) {
}

addQueryHelpers({
replace: jora.methods.replace,
parseExample,
slug(current) {
return current ? slugger.slug(current, { dryrun: true }) : '';
Expand Down
101 changes: 63 additions & 38 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { version } from './version.js';
import { hasOwn } from './utils/misc.js';
import parser from './lang/parse.js';
import suggest from './lang/suggest.js';
import walk from './lang/walk.js';
Expand All @@ -14,6 +15,52 @@ const cacheStrictStat = new Map();
const cacheTollerant = new Map();
const cacheTollerantStat = new Map();

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) {
console.log(`[${sectionName}]`);
if (typeof value === 'string') {
Expand Down Expand Up @@ -89,8 +136,8 @@ function createQuery(source, options) {

const statMode = Boolean(options.stat);
const tolerantMode = Boolean(options.tolerant);
const localMethods = options.methods ? { ...methods, ...options.methods } : methods;
const localAssetions = options.assertions ? { ...assertions, ...options.assertions } : assertions;
const localMethods = 0 && options.methods ? { ...methods, ...options.methods } : methods;
const localAssetions = 0 && options.assertions ? { ...assertions, ...options.assertions } : assertions;
const cache = statMode
? (tolerantMode ? cacheTollerantStat : cacheStrictStat)
: (tolerantMode ? cacheTollerant : cacheStrict);
Expand All @@ -112,45 +159,14 @@ function createQuery(source, options) {
: fn;
}

function setup(customMethods, customAssertions) {
function setup(options) {
const cacheStrict = new Map();
const cacheStrictStat = new Map();
const cacheTollerant = new Map();
const cacheTollerantStat = new Map();
const localMethods = { ...methods };
const localAssetions = { ...assertions };

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, localAssetions);
const value = current => compiledFn(current, null);
Object.defineProperty(localMethods, name, { value });
return value;
}
});
} else {
localMethods[name] = fn;
}
}

for (const [name, fn] of Object.entries(customAssertions || {})) {
if (typeof fn === 'string') {
Object.defineProperty(localAssetions, name, {
configurable: true,
get() {
const compiledFn = compileFunction(fn)(buildin, localMethods, localAssetions);
const value = current => compiledFn(current, null);
Object.defineProperty(localAssetions, name, { value });
return value;
}
});
} else {
localAssetions[name] = fn;
}
}
const { methods: customMethods, assertions: customAssertions } = options || {};
const { queryMethods, queryAssertions } =
buildQueryMethodsAndAssertions(customMethods, customAssertions);

return function query(source, options) {
options = options || {};
Expand All @@ -167,7 +183,16 @@ function setup(customMethods, customAssertions) {
if (cache.has(source) && !options.debug) {
fn = cache.get(source);
} else {
const perform = compileFunction(source, statMode, tolerantMode, options.debug)(buildin, localMethods, localAssetions);
const perform = compileFunction(
source,
statMode,
tolerantMode,
options.debug
)(
buildin,
queryMethods,
queryAssertions
);
fn = statMode
? Object.assign((data, context) => createStatApi(source, perform(data, context)), { query: perform })
: perform;
Expand Down
5 changes: 4 additions & 1 deletion src/lang/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,10 @@ export default function compile(ast, tolerant = false, suggestions = null) {
if (!hasOwn(providedMethods, method)) {
return () => {
throw Object.assign(
new Error(`Method "${method}" is not defined`),
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 } } }
);
};
Expand Down
Loading

0 comments on commit d44456d

Please sign in to comment.