From c2630e20b491dd93ef00cbc47e1c684d222ce426 Mon Sep 17 00:00:00 2001 From: sedinkinya Date: Thu, 31 Mar 2022 15:28:05 -0400 Subject: [PATCH 01/10] Fixed issues related to the repeat function LF-2221, LF-2209 --- CHANGELOG.md | 7 ++++ package-lock.json | 2 +- package.json | 2 +- src/combining.js | 6 ++-- src/existence.js | 19 ++--------- src/fhirpath.js | 2 +- src/filtering.js | 34 +++++++++++++------- test/cases/5.2.3_repeat.yaml | 2 ++ test/cases/5.2_filtering_and_projection.yaml | 8 ++--- 9 files changed, 44 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a19d09..1296f37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ This log documents significant changes for each release. This project follows [Semantic Versioning](http://semver.org/). +## [2.14.1] - 2022-03-31 +### Fixed +- Previously, the `repeat` function changed the contents of the `%someVar` value + when used in a `%someVar.repeat(...)` expression. +- Previously, `a.repeat('b')` went into an infinite loop. +- The `repeat` function should no longer return duplicates. + ## [2.14.0] - 2022-03-02 ### Added - Function to get the intersection of two collections: intersect(). diff --git a/package-lock.json b/package-lock.json index 05ea8ec..6c68a31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "fhirpath", - "version": "2.14.0", + "version": "2.14.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index f7d0a96..800bda4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fhirpath", - "version": "2.14.0", + "version": "2.14.1", "description": "A FHIRPath engine", "main": "src/fhirpath.js", "dependencies": { diff --git a/src/combining.js b/src/combining.js index 52f80ca..ced19da 100644 --- a/src/combining.js +++ b/src/combining.js @@ -1,11 +1,11 @@ // This file holds code to hande the FHIRPath Combining functions. const combineFns = {}; -const existence = require('./existence'); +const {distinctFn} = require('./filtering'); const deepEqual = require('./deep-equal'); combineFns.union = function(coll1, coll2){ - return existence.distinctFn(coll1.concat(coll2)); + return distinctFn(coll1.concat(coll2)); }; combineFns.combineFn = function(coll1, coll2){ @@ -15,7 +15,7 @@ combineFns.combineFn = function(coll1, coll2){ combineFns.intersect = function(coll1, coll2) { let result = []; if (coll1.length && coll2.length) { - result = existence.distinctFn(coll1).filter( + result = distinctFn(coll1).filter( obj1 => coll2.some(obj2 => deepEqual(obj1, obj2)) ); } diff --git a/src/existence.js b/src/existence.js index ee1d95f..1e656e3 100644 --- a/src/existence.js +++ b/src/existence.js @@ -2,7 +2,7 @@ // specification). const util = require("./utilities"); -const filtering = require("./filtering"); +const {whereMacro, distinctFn} = require("./filtering"); const misc = require("./misc"); const deepEqual = require('./deep-equal'); @@ -17,7 +17,7 @@ engine.notFn = function(coll) { engine.existsMacro = function(coll, expr) { var vec = coll; if (expr) { - return engine.existsMacro(filtering.whereMacro(coll, expr)); + return engine.existsMacro(whereMacro(coll, expr)); } return !util.isEmpty(vec); }; @@ -92,20 +92,7 @@ engine.supersetOfFn = function(coll1, coll2) { }; engine.isDistinctFn = function(x) { - return [x.length === engine.distinctFn(x).length]; -}; - -engine.distinctFn = function(x) { - let unique = []; - if (x.length > 0) { - x = x.concat(); - do { - let xObj = x.shift(); - unique.push(xObj); - x = x.filter(o => !deepEqual(xObj, o)); - } while (x.length); - } - return unique; + return [x.length === distinctFn(x).length]; }; module.exports = engine; diff --git a/src/fhirpath.js b/src/fhirpath.js index a8bd379..f4f2d78 100644 --- a/src/fhirpath.js +++ b/src/fhirpath.js @@ -72,7 +72,7 @@ engine.invocationTable = { subsetOf: {fn: existence.subsetOfFn, arity: {1: ["AnyAtRoot"]}}, supersetOf: {fn: existence.supersetOfFn, arity: {1: ["AnyAtRoot"]}}, isDistinct: {fn: existence.isDistinctFn}, - distinct: {fn: existence.distinctFn}, + distinct: {fn: filtering.distinctFn}, count: {fn: aggregate.countFn}, where: {fn: filtering.whereMacro, arity: {1: ["Expr"]}}, extension: {fn: filtering.extension, arity: {1: ["String"]}}, diff --git a/src/filtering.js b/src/filtering.js index 55a3534..69748c8 100644 --- a/src/filtering.js +++ b/src/filtering.js @@ -6,6 +6,7 @@ */ const util = require('./utilities'); const {TypeInfo, ResourceNode} = require('./types'); +const deepEqual = require('./deep-equal'); var engine = {}; engine.whereMacro = function(parentData, expr) { @@ -43,18 +44,15 @@ engine.selectMacro = function(data, expr) { engine.repeatMacro = function(parentData, expr) { if(parentData !== false && ! parentData) { return []; } - var res = []; - var items = parentData; - - var next = null; - var lres = null; - while (items.length != 0) { - next = items.shift(); - lres = expr(next); - if(lres){ - res = res.concat(lres); - items = items.concat(lres); - } + let res = []; + const length = parentData.length; + for(let i = 0; i < length; ++i) { + let newItems = parentData[i]; + do { + newItems = engine.distinctFn(expr(newItems)) + .filter(item => !res.find(r => deepEqual(r, item))); + res = res.concat(newItems); + } while (!util.isEmpty(newItems)); } return res; }; @@ -98,5 +96,17 @@ engine.ofTypeFn = function(coll, typeInfo) { }); }; +engine.distinctFn = function(x) { + let unique = []; + if (x.length > 0) { + x = x.concat(); + do { + let xObj = x.shift(); + unique.push(xObj); + x = x.filter(o => !deepEqual(xObj, o)); + } while (x.length); + } + return unique; +}; module.exports = engine; diff --git a/test/cases/5.2.3_repeat.yaml b/test/cases/5.2.3_repeat.yaml index 7e19a83..e3d88e1 100644 --- a/test/cases/5.2.3_repeat.yaml +++ b/test/cases/5.2.3_repeat.yaml @@ -1,6 +1,8 @@ tests: - expression: Questionnaire.repeat(item).linkId result: ["1", "2", "1.1", "2.1", "1.1.1", "2.1.2", "1.1.1.1", "1.1.1.2", "1.1.1.1.1", "1.1.1.1.2"] + - expression: Questionnaire.combine(Questionnaire).repeat(item).linkId + result: ["1", "2", "1.1", "2.1", "1.1.1", "2.1.2", "1.1.1.1", "1.1.1.2", "1.1.1.1.1", "1.1.1.1.2"] subject: resourceType: Questionnaire id: '3141' diff --git a/test/cases/5.2_filtering_and_projection.yaml b/test/cases/5.2_filtering_and_projection.yaml index e27a2e4..688e1cc 100644 --- a/test/cases/5.2_filtering_and_projection.yaml +++ b/test/cases/5.2_filtering_and_projection.yaml @@ -67,9 +67,9 @@ tests: # which would find any descendants called group or question, not just the ones nested inside other group or question elements. - desc: '* traverse tree' - desc: '** find all true values in nested coll' - - desc: 'TODO FIX INF LOOP' - # expression: Functions.coll1.colltrue.repeat(true) - # result: [true, true, true] + - desc: 'FIX INF LOOP' + expression: Functions.coll1.colltrue.repeat(true) + result: [true] - desc: '** find all attrs' expression: Functions.repeat(repeatingAttr).count() result: [2] @@ -80,7 +80,7 @@ tests: - desc: '** find all true values in nested coll' expression: Functions.coll1.colltrue.repeat(attr) - result: [true, true, true] + result: [true] - desc: '** find non-exists value' expression: Functions.coll1.repeat(nothing) From adcdd7a1fde526f54ff98712ed9ca5616fc624b5 Mon Sep 17 00:00:00 2001 From: sedinkinya Date: Thu, 31 Mar 2022 18:05:10 -0400 Subject: [PATCH 02/10] Fixed issues found during review LF-2221, LF-2209 --- src/filtering.js | 5 ++--- test/api.test.js | 17 +++++++++++++++++ test/cases/5.2_filtering_and_projection.yaml | 5 ++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/filtering.js b/src/filtering.js index 69748c8..89b740c 100644 --- a/src/filtering.js +++ b/src/filtering.js @@ -50,9 +50,8 @@ engine.repeatMacro = function(parentData, expr) { let newItems = parentData[i]; do { newItems = engine.distinctFn(expr(newItems)) - .filter(item => !res.find(r => deepEqual(r, item))); - res = res.concat(newItems); - } while (!util.isEmpty(newItems)); + .filter(item => !res.some(r => deepEqual(r, item))); + } while (-res.length + res.push.apply(res, newItems)); } return res; }; diff --git a/test/api.test.js b/test/api.test.js index 5d97662..53fc1c0 100644 --- a/test/api.test.js +++ b/test/api.test.js @@ -1,5 +1,6 @@ const fhirpath = require('../src/fhirpath'); const r4_model = require('../fhir-context/r4'); +const _ = require('lodash'); describe('compile', () => { it('should accept a model object', () => { @@ -52,4 +53,20 @@ describe('evaluate', () => { ); expect(result).toStrictEqual([true]); }); + + it('should not change the context variable during expression evaluation', () => { + const someVar = fhirpath.evaluate( + require('../test/resources/quantity-example.json'), + 'QuestionnaireResponse' + ); + const someVarOrig = _.cloneDeep(someVar); + let result = fhirpath.evaluate( + {}, + "%someVar.repeat(item).linkId", + {someVar}, + r4_model + ); + expect(result).toEqual(['1', '2', '3']); + expect(someVar).toStrictEqual(someVarOrig); + }) }); diff --git a/test/cases/5.2_filtering_and_projection.yaml b/test/cases/5.2_filtering_and_projection.yaml index 688e1cc..c0a7366 100644 --- a/test/cases/5.2_filtering_and_projection.yaml +++ b/test/cases/5.2_filtering_and_projection.yaml @@ -67,9 +67,12 @@ tests: # which would find any descendants called group or question, not just the ones nested inside other group or question elements. - desc: '* traverse tree' - desc: '** find all true values in nested coll' - - desc: 'FIX INF LOOP' + - desc: 'FIX INF LOOP 1' expression: Functions.coll1.colltrue.repeat(true) result: [true] + - desc: 'FIX INF LOOP 2' + expression: (1 | 2).repeat('item') + result: ['item'] - desc: '** find all attrs' expression: Functions.repeat(repeatingAttr).count() result: [2] From 0886381fdeb9853f70c0954e2c4fdc69b0f322c0 Mon Sep 17 00:00:00 2001 From: sedinkinya Date: Thu, 31 Mar 2022 18:15:23 -0400 Subject: [PATCH 03/10] Fixed issues found during review LF-2221, LF-2209 --- src/filtering.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filtering.js b/src/filtering.js index 89b740c..c9f22d2 100644 --- a/src/filtering.js +++ b/src/filtering.js @@ -51,7 +51,7 @@ engine.repeatMacro = function(parentData, expr) { do { newItems = engine.distinctFn(expr(newItems)) .filter(item => !res.some(r => deepEqual(r, item))); - } while (-res.length + res.push.apply(res, newItems)); + } while (res.length < res.push.apply(res, newItems)); } return res; }; From 7a9c9f5b5b03c703b479a51de6e45c659e00dc0d Mon Sep 17 00:00:00 2001 From: sedinkinya Date: Fri, 1 Apr 2022 09:56:57 -0400 Subject: [PATCH 04/10] npm audit fix --- demo/package-lock.json | 371 +++++++++++------------------------------ demo/package.json | 2 +- package-lock.json | 12 +- 3 files changed, 105 insertions(+), 280 deletions(-) diff --git a/demo/package-lock.json b/demo/package-lock.json index 7382640..3944cae 100644 --- a/demo/package-lock.json +++ b/demo/package-lock.json @@ -498,12 +498,6 @@ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, "antlr4": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.8.0.tgz", @@ -548,17 +542,6 @@ "lodash": "^4.17.14" } }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - } - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -760,19 +743,6 @@ "upper-case": "^1.1.1" } }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -835,21 +805,6 @@ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.61.1.tgz", "integrity": "sha512-+D1NZjAucuzE93vJGbAaXzvoBHwp9nJZWWWF9utjv25+5AZUiah6CIlfb4ikG4MoDsFsCG8niiJH5++OO2LgIQ==" }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, "colorette": { "version": "2.0.16", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", @@ -977,23 +932,19 @@ } }, "css-loader": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-1.0.1.tgz", - "integrity": "sha512-+ZHAZm/yqvJ2kDtPne3uX0C+Vr3Zn5jFn2N4HywtS5ujwvsVkyg0VArEXpl3BgczDA8anieki1FIzhchX4yrDw==", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", "dev": true, "requires": { - "babel-code-frame": "^6.26.0", - "css-selector-tokenizer": "^0.7.0", - "icss-utils": "^2.1.0", - "loader-utils": "^1.0.2", - "lodash": "^4.17.11", - "postcss": "^6.0.23", - "postcss-modules-extract-imports": "^1.2.0", - "postcss-modules-local-by-default": "^1.2.0", - "postcss-modules-scope": "^1.1.0", - "postcss-modules-values": "^1.3.0", - "postcss-value-parser": "^3.3.0", - "source-list-map": "^2.0.0" + "icss-utils": "^5.1.0", + "postcss": "^8.4.7", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.5" } }, "css-select": { @@ -1009,17 +960,6 @@ "nth-check": "^2.0.0" } }, - "css-selector-tokenizer": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz", - "integrity": "sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "fastparse": "^1.1.2", - "regexpu-core": "^4.6.0" - } - }, "css-what": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", @@ -1321,12 +1261,6 @@ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", "dev": true }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -1365,12 +1299,6 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -1566,12 +1494,6 @@ "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", "dev": true }, - "fastparse": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", - "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", - "dev": true - }, "fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -1778,21 +1700,6 @@ "function-bind": "^1.1.1" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", @@ -2057,20 +1964,11 @@ "safer-buffer": ">= 2.1.2 < 3" } }, - "icss-replace-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", - "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", - "dev": true - }, "icss-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", - "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", - "dev": true, - "requires": { - "postcss": "^6.0.1" - } + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true }, "ignore": { "version": "5.1.8", @@ -2303,12 +2201,6 @@ } } }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, "js-yaml": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", @@ -2318,12 +2210,6 @@ "esprima": "^4.0.0" } }, - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - }, "json-loader": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", @@ -2661,6 +2547,15 @@ "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", "dev": true }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -2753,9 +2648,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "mkdirp": { @@ -2817,9 +2712,9 @@ } }, "node-forge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", - "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "dev": true }, "normalize-path": { @@ -3072,6 +2967,12 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -3123,90 +3024,73 @@ } }, "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "version": "8.4.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.12.tgz", + "integrity": "sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==", "dev": true, "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "nanoid": "^3.3.1", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } + "nanoid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.2.tgz", + "integrity": "sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==", + "dev": true } } }, "postcss-modules-extract-imports": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz", - "integrity": "sha512-6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw==", - "dev": true, - "requires": { - "postcss": "^6.0.1" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true }, "postcss-modules-local-by-default": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", - "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", "dev": true, "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" } }, "postcss-modules-scope": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", - "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", "dev": true, "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" + "postcss-selector-parser": "^6.0.4" } }, "postcss-modules-values": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", - "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", "dev": true, "requires": { - "icss-replace-symbols": "^1.1.0", - "postcss": "^6.0.1" + "icss-utils": "^5.0.0" + } + }, + "postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" } }, "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, "pretty-error": { @@ -3360,21 +3244,6 @@ "resolve": "^1.9.0" } }, - "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", - "dev": true - }, - "regenerate-unicode-properties": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", - "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", - "dev": true, - "requires": { - "regenerate": "^1.4.0" - } - }, "regexp.prototype.flags": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz", @@ -3385,35 +3254,6 @@ "define-properties": "^1.1.3" } }, - "regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", - "dev": true, - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - } - }, - "regjsgen": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", - "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", - "dev": true - }, - "regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - } - }, "relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", @@ -3557,6 +3397,15 @@ "node-forge": "^1.2.0" } }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, "send": { "version": "0.17.2", "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", @@ -3738,6 +3587,12 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, "source-map-support": { "version": "0.5.19", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", @@ -3916,12 +3771,6 @@ "integrity": "sha512-yu+WNa/nSbFa+VBeR2KibfCeIQSKh/aD7G5eFD4Rx4W36MWE3G6SzU3BixDOArLv56u2bz6YEePsHSsioojuXw==", "dev": true }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -4080,34 +3929,6 @@ } } }, - "unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", - "dev": true - }, - "unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", - "dev": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", - "dev": true - }, - "unicode-property-aliases-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", - "dev": true - }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -5263,6 +5084,12 @@ "integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==", "dev": true }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "yaml-loader": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/yaml-loader/-/yaml-loader-0.5.0.tgz", diff --git a/demo/package.json b/demo/package.json index 0da0198..ea48132 100644 --- a/demo/package.json +++ b/demo/package.json @@ -9,7 +9,7 @@ "yaml-loader": "^0.5.0" }, "devDependencies": { - "css-loader": "^1.0.0", + "css-loader": "^6.7.1", "file-loader": "^1.1.11", "html-webpack-plugin": "^3.2.0", "js-yaml": "^3.13.1", diff --git a/package-lock.json b/package-lock.json index 6c68a31..6a78272 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10114,9 +10114,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "minipass": { @@ -10771,8 +10771,7 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "resolved": "", "dev": true }, "strip-ansi": { @@ -10862,8 +10861,7 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "resolved": "", "dev": true }, "strip-ansi": { From 6e289a69f299b8f6652b21b7fbbeeabeecd9b0d1 Mon Sep 17 00:00:00 2001 From: sedinkinya Date: Tue, 5 Apr 2022 20:20:58 -0400 Subject: [PATCH 05/10] Add hash comparison to avoid O(n**2) LF-2227 --- CHANGELOG.md | 4 +- src/combining.js | 29 +++++++++-- src/existence.js | 16 +++--- src/filtering.js | 31 ++++++++---- src/hash-object.js | 53 ++++++++++++++++++++ src/numbers.js | 6 +-- test/cases/5.1_existence.yaml | 9 ++++ test/cases/5.2_filtering_and_projection.yaml | 13 +++-- test/cases/5.3_subsetting.yaml | 14 ++++++ test/cases/5.4_combining.yaml | 10 ++++ 10 files changed, 157 insertions(+), 28 deletions(-) create mode 100644 src/hash-object.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 1296f37..7b8f546 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,14 @@ This log documents significant changes for each release. This project follows when used in a `%someVar.repeat(...)` expression. - Previously, `a.repeat('b')` went into an infinite loop. - The `repeat` function should no longer return duplicates. +- Optimized functions: `distinct`, `union`, `subsetOf`, `intersect`, and `repeat` + by changing complexity of the algorithm from O(n**2) to O(n). ## [2.14.0] - 2022-03-02 ### Added - Function to get the intersection of two collections: intersect(). ### Fixed -- The distinct, union, subsetOf, and intersect functions now use +- The `distinct`, `union`, `subsetOf`, and `intersect` functions now use the "6.1.1. = (Equals)" function to compare collection items instead of using a map with JSON keys, which can affect their performance because the complexity of the algorithm has changed from O(n) to O(n**2). diff --git a/src/combining.js b/src/combining.js index ced19da..052d009 100644 --- a/src/combining.js +++ b/src/combining.js @@ -2,7 +2,7 @@ const combineFns = {}; const {distinctFn} = require('./filtering'); -const deepEqual = require('./deep-equal'); +const hashObject = require('./hash-object'); combineFns.union = function(coll1, coll2){ return distinctFn(coll1.concat(coll2)); @@ -14,10 +14,29 @@ combineFns.combineFn = function(coll1, coll2){ combineFns.intersect = function(coll1, coll2) { let result = []; - if (coll1.length && coll2.length) { - result = distinctFn(coll1).filter( - obj1 => coll2.some(obj2 => deepEqual(obj1, obj2)) - ); + const coll1Length = coll1.length; + let uncheckedLength = coll2.length; + + if (coll1Length && uncheckedLength) { + let coll2hash = {}; + coll2.forEach(item => { + const hash = hashObject(item); + if (coll2hash[hash]) { + uncheckedLength--; + } else { + coll2hash[hash] = true; + } + }); + + for (let i=0; i 0; ++i) { + let item = coll1[i]; + let hash = hashObject(item); + if (coll2hash[hash]) { + result.push(item); + coll2hash[hash] = false; + uncheckedLength--; + } + } } return result; diff --git a/src/existence.js b/src/existence.js index 1e656e3..bc774b8 100644 --- a/src/existence.js +++ b/src/existence.js @@ -4,7 +4,7 @@ const util = require("./utilities"); const {whereMacro, distinctFn} = require("./filtering"); const misc = require("./misc"); -const deepEqual = require('./deep-equal'); +const hashObject = require('./hash-object'); const engine = {}; engine.emptyFn = util.isEmpty; @@ -73,12 +73,14 @@ engine.anyFalseFn = function(x) { * Returns true if coll1 is a subset of coll2. */ function subsetOf(coll1, coll2) { - let rtn = coll1.length <= coll2.length; - if (rtn) { - for (let p=0, pLen=coll1.length; p deepEqual(obj1, util.valData(obj2))); - } + const coll1Length = coll1.length; + let rtn = coll1Length <= coll2.length; + if (rtn && coll1Length) { + const c2Hash = coll2.reduce((hash, item) => { + hash[hashObject(item)] = true; + return hash; + }, {}); + rtn = !coll1.some(item => !c2Hash[hashObject(item)]); } return rtn; } diff --git a/src/filtering.js b/src/filtering.js index c9f22d2..f0c1b65 100644 --- a/src/filtering.js +++ b/src/filtering.js @@ -6,7 +6,7 @@ */ const util = require('./utilities'); const {TypeInfo, ResourceNode} = require('./types'); -const deepEqual = require('./deep-equal'); +const hashObject = require('./hash-object'); var engine = {}; engine.whereMacro = function(parentData, expr) { @@ -45,12 +45,20 @@ engine.repeatMacro = function(parentData, expr) { if(parentData !== false && ! parentData) { return []; } let res = []; + const unique = {}; const length = parentData.length; for(let i = 0; i < length; ++i) { let newItems = parentData[i]; do { - newItems = engine.distinctFn(expr(newItems)) - .filter(item => !res.some(r => deepEqual(r, item))); + newItems = expr(newItems) + .filter(item => { + const key = hashObject(item); + const isUnique = !unique[key]; + if (isUnique) { + unique[key] = true; + } + return isUnique; + }); } while (res.length < res.push.apply(res, newItems)); } return res; @@ -97,13 +105,18 @@ engine.ofTypeFn = function(coll, typeInfo) { engine.distinctFn = function(x) { let unique = []; + // Since this requires a deep equals, use a hash table (on JSON strings) for + // efficiency. if (x.length > 0) { - x = x.concat(); - do { - let xObj = x.shift(); - unique.push(xObj); - x = x.filter(o => !deepEqual(xObj, o)); - } while (x.length); + let uniqueHash = {}; + for (let i=0, len=x.length; i { + const v = value[key]; + o[key] = prepareObject(v); + return o; + }, {}); + } + + return value; +} + +module.exports = hashObject; diff --git a/src/numbers.js b/src/numbers.js index 3eb335f..f5225cf 100644 --- a/src/numbers.js +++ b/src/numbers.js @@ -42,9 +42,9 @@ const PRECISION_STEP = 1e-8; /** * Rounds a number to the nearest multiple of PRECISION_STEP. */ -function roundToMaxPrecision(x) { +const roundToMaxPrecision = numberFns.roundToMaxPrecision = function (x) { return Math.round(x/PRECISION_STEP)*PRECISION_STEP; -} +}; /** * Determines numbers equivalence @@ -79,4 +79,4 @@ numberFns.isEqual = function(actual, expected) { return roundToMaxPrecision(actual) === roundToMaxPrecision(expected); }; -module.exports = numberFns; \ No newline at end of file +module.exports = numberFns; diff --git a/test/cases/5.1_existence.yaml b/test/cases/5.1_existence.yaml index 13d7cff..82050dc 100644 --- a/test/cases/5.1_existence.yaml +++ b/test/cases/5.1_existence.yaml @@ -336,6 +336,15 @@ tests: - prop1: 5 prop2: 6 + - desc: '** should use year-to-month conversion factor (https://hl7.org/fhirpath/#equals)' + expression: (1 year).combine(12 months).distinct() + result: + - 1 year + + - desc: '** should compare quantities for equality (https://hl7.org/fhirpath/#equals)' + expression: (3 'min').combine(180 seconds).distinct() + result: + - 3 'min' - desc: '5.1.13. count() : integer' # Returns a collection with a single value which is diff --git a/test/cases/5.2_filtering_and_projection.yaml b/test/cases/5.2_filtering_and_projection.yaml index c0a7366..e788a2a 100644 --- a/test/cases/5.2_filtering_and_projection.yaml +++ b/test/cases/5.2_filtering_and_projection.yaml @@ -66,13 +66,20 @@ tests: # Questionnaire.descendants().select(group | question) # which would find any descendants called group or question, not just the ones nested inside other group or question elements. - desc: '* traverse tree' - - desc: '** find all true values in nested coll' - - desc: 'FIX INF LOOP 1' + - desc: '** should not result in an infinite loop 1' expression: Functions.coll1.colltrue.repeat(true) result: [true] - - desc: 'FIX INF LOOP 2' + - desc: '** should not result in an infinite loop 2' expression: (1 | 2).repeat('item') result: ['item'] + - desc: '** should use year-to-month conversion factor (https://hl7.org/fhirpath/#equals)' + expression: (1 year).combine(12 months).repeat($this) + result: + - 1 year + - desc: '** should compare quantities for equality (https://hl7.org/fhirpath/#equals)' + expression: (3 'min').combine(180 seconds).repeat($this) + result: + - 3 'min' - desc: '** find all attrs' expression: Functions.repeat(repeatingAttr).count() result: [2] diff --git a/test/cases/5.3_subsetting.yaml b/test/cases/5.3_subsetting.yaml index 2c20bb5..c761a2a 100644 --- a/test/cases/5.3_subsetting.yaml +++ b/test/cases/5.3_subsetting.yaml @@ -178,6 +178,16 @@ tests: - prop1: 1 prop2: 2 + - desc: '** should use year-to-month conversion factor (https://hl7.org/fhirpath/#equals)' + expression: (1 year).combine(12 months).intersect(12 months) + result: + - 1 year + + - desc: '** should compare quantities for equality (https://hl7.org/fhirpath/#equals)' + expression: (3 'min').combine(180 seconds).intersect(180 seconds) + result: + - 3 'min' + subject: resourceType: Functions attrempty: [] @@ -225,11 +235,15 @@ subject: - attr: 4 days objects: group1: + - prop1: 1 + prop2: 2 - prop1: 1 prop2: 2 - prop1: 3 prop2: 4 group2: + - prop2: 2 + prop1: 1 - prop2: 2 prop1: 1 - prop1: 5 diff --git a/test/cases/5.4_combining.yaml b/test/cases/5.4_combining.yaml index 78aa85c..7107c87 100644 --- a/test/cases/5.4_combining.yaml +++ b/test/cases/5.4_combining.yaml @@ -20,6 +20,16 @@ tests: expression: Functions.attrdouble | Functions.coll1.coll2.attr result: [1, 2, 3, 4, 5] + - desc: '** should use year-to-month conversion factor (https://hl7.org/fhirpath/#equals)' + expression: (1 year | 12 months) + result: + - 1 year + + - desc: '** should compare quantities for equality (https://hl7.org/fhirpath/#equals)' + expression: (3 'min' | 180 seconds) + result: + - 3 'min' + - desc: '** should not depend on the order of properties in an object' expression: Functions.objects.group1 | Functions.objects.group2 result: From 326a042126e3b2c40cd778a1414aeafdfa0fed57 Mon Sep 17 00:00:00 2001 From: sedinkinya Date: Tue, 5 Apr 2022 20:58:47 -0400 Subject: [PATCH 06/10] Add version info to fhirpath.js LF-2215 --- CHANGELOG.md | 2 ++ browser-build/package-json-loader.js | 6 ++++++ browser-build/webpack.config.js | 9 +++++++++ src/fhirpath.js | 2 ++ 4 files changed, 19 insertions(+) create mode 100644 browser-build/package-json-loader.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b8f546..dd470d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ This log documents significant changes for each release. This project follows [Semantic Versioning](http://semver.org/). ## [2.14.1] - 2022-03-31 +### Added +- Added a "version" field with the release version to the object exported by fhirpath.js. ### Fixed - Previously, the `repeat` function changed the contents of the `%someVar` value when used in a `%someVar.repeat(...)` expression. diff --git a/browser-build/package-json-loader.js b/browser-build/package-json-loader.js new file mode 100644 index 0000000..7d4cfc8 --- /dev/null +++ b/browser-build/package-json-loader.js @@ -0,0 +1,6 @@ +// Removes all content except version. +// When we import package.json we need only the application version. +module.exports = function loader(source) { + const { version } = JSON.parse(source); + return JSON.stringify({ version }); +}; diff --git a/browser-build/webpack.config.js b/browser-build/webpack.config.js index a34f50c..cf97c19 100644 --- a/browser-build/webpack.config.js +++ b/browser-build/webpack.config.js @@ -1,4 +1,5 @@ const CopyPlugin = require('copy-webpack-plugin'); +const path = require('path'); /** * Returns a base webpack configuration object with settings used by more than @@ -30,6 +31,14 @@ function makeBaseConfig() { ]] } } + }, + { + test: /package.json$/, + use: [ + { + loader: path.resolve('package-json-loader.js') + } + ] } ] } diff --git a/src/fhirpath.js b/src/fhirpath.js index f4f2d78..ae56a77 100644 --- a/src/fhirpath.js +++ b/src/fhirpath.js @@ -28,6 +28,7 @@ // for strings and numbers // we can make dispatching params type dependent - let see +const {version} = require('../package.json'); const parser = require("./parser"); const util = require("./utilities"); require("./polyfill"); @@ -717,6 +718,7 @@ function compile(path, model) { } module.exports = { + version, parse, compile, evaluate, From 59e3882a02eda01384978571aadf88b732be6dc3 Mon Sep 17 00:00:00 2001 From: sedinkinya Date: Tue, 5 Apr 2022 21:13:36 -0400 Subject: [PATCH 07/10] npm audit fix --- package-lock.json | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6a78272..2e96356 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10770,8 +10770,9 @@ }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true }, "strip-ansi": { @@ -10860,8 +10861,9 @@ }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true }, "strip-ansi": { @@ -10870,7 +10872,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^3.0.1" } } } From 8695d27fa100938369b096cb45f5e7c3eb59836a Mon Sep 17 00:00:00 2001 From: sedinkinya Date: Tue, 5 Apr 2022 21:32:44 -0400 Subject: [PATCH 08/10] Fixed comment. LF-2227 --- src/hash-object.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hash-object.js b/src/hash-object.js index 2428898..9d2664d 100644 --- a/src/hash-object.js +++ b/src/hash-object.js @@ -7,8 +7,8 @@ const {FP_Type, FP_Quantity} = require('./types'); * Returns a JSON version of the given object, but with the object's keys * in sorted order (or at least stable order, * see https://stackoverflow.com/a/35810961/360782) and the values in - * unified forms, e.g. "1 year" is converted to "12 months", "3 'min'" - * is converted to "120 'sec'". + * unified forms, e.g. "1 year" is converted to the same value as "12 months", + * "3 'min'" is converted to the same value as "120 'sec'". */ function hashObject(obj) { return JSON.stringify(prepareObject(obj)); From 206fd0bbf033bfe16a65999670e1cbcb2d0776a3 Mon Sep 17 00:00:00 2001 From: sedinkinya Date: Thu, 7 Apr 2022 11:42:57 -0400 Subject: [PATCH 09/10] Fixed issues found during review LF-2227 --- src/deep-equal.js | 18 +++++++++++++++--- src/filtering.js | 4 ++-- src/hash-object.js | 4 ++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/deep-equal.js b/src/deep-equal.js index 3f3c30c..a30726e 100644 --- a/src/deep-equal.js +++ b/src/deep-equal.js @@ -24,8 +24,20 @@ function normalizeStr(x) { return x.toUpperCase().replace(/\s+/, ' '); } - -var deepEqual = function (actual, expected, opts) { +/** + * Performs a deep comparison between two values to determine if they are equal. + * When you need to compare many objects, you can use hashObject instead for + * optimization (if changes are needed here, they are likely also needed there). + * @param {any} actual - one of the comparing objects + * @param {any} expected - one of the comparing objects + * @param {Object} [opts] - comparison options + * @param {boolean} [opts.fuzzy] - false (by default), if comparing objects for + * equality (see https://hl7.org/fhirpath/#equals). + * true, if comparing objects for equivalence + * (see https://hl7.org/fhirpath/#equivalent). + * @return {boolean} + */ +function deepEqual(actual, expected, opts) { actual = util.valData(actual); expected = util.valData(expected); if (!opts) opts = {}; @@ -96,7 +108,7 @@ var deepEqual = function (actual, expected, opts) { // accounts for both named and indexed properties on Arrays. return objEquiv(actual, expected, opts); } -}; +} function isUndefinedOrNull(value) { return value === null || value === undefined; diff --git a/src/filtering.js b/src/filtering.js index f0c1b65..0c2824e 100644 --- a/src/filtering.js +++ b/src/filtering.js @@ -112,9 +112,9 @@ engine.distinctFn = function(x) { for (let i=0, len=x.length; i Date: Tue, 12 Apr 2022 10:28:36 -0400 Subject: [PATCH 10/10] Fixed incorrect styles for the demo app --- demo/package-lock.json | 12 ++++-------- demo/package.json | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/demo/package-lock.json b/demo/package-lock.json index 3944cae..6c49a39 100644 --- a/demo/package-lock.json +++ b/demo/package-lock.json @@ -3756,14 +3756,10 @@ "dev": true }, "style-loader": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.21.0.tgz", - "integrity": "sha512-T+UNsAcl3Yg+BsPKs1vd22Fr8sVT+CJMtzqc6LEw9bbJZb43lm9GoeIfUcDEefBSWC0BhYbcdupV1GtI4DGzxg==", - "dev": true, - "requires": { - "loader-utils": "^1.1.0", - "schema-utils": "^0.4.5" - } + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", + "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", + "dev": true }, "superstruct": { "version": "0.12.2", diff --git a/demo/package.json b/demo/package.json index ea48132..f112e3b 100644 --- a/demo/package.json +++ b/demo/package.json @@ -13,7 +13,7 @@ "file-loader": "^1.1.11", "html-webpack-plugin": "^3.2.0", "js-yaml": "^3.13.1", - "style-loader": "^0.21.0", + "style-loader": "^3.3.1", "webpack": "^5.11.1", "webpack-cli": "^4.9.1", "webpack-dev-server": "^4.7.3",