From cedc04f20afaa50168f1363e7b04f2e031ddc258 Mon Sep 17 00:00:00 2001 From: Edward-Precious Omegbu Date: Sat, 28 Jun 2025 19:29:20 +0100 Subject: [PATCH 1/8] fix: eslint and prettier config updated to allow endOfLine as auto --- .eslintrc.cjs | 4 ++++ .prettierrc.cjs | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index fb66ab5c16..de186937d6 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -43,6 +43,10 @@ module.exports = { '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' } + ], + 'prettier/prettier': [ + 'error', + { endOfLine: 'auto' } ] }, plugins: ['@typescript-eslint'], diff --git a/.prettierrc.cjs b/.prettierrc.cjs index 2c815ce252..d4ac8de058 100644 --- a/.prettierrc.cjs +++ b/.prettierrc.cjs @@ -1,5 +1,6 @@ module.exports = { semi: false, singleQuote: true, - trailingComma: 'none' + trailingComma: 'none', + endOfLine: 'auto' } From 70de523a80cfe1405c260469b08ca5c4bd1e67cb Mon Sep 17 00:00:00 2001 From: Edward-Precious Omegbu Date: Mon, 30 Jun 2025 01:59:27 +0100 Subject: [PATCH 2/8] fix: numeric literals can be set as object keys and used to access objects --- gulpfile.js | 2 +- src/expression/node/AccessorNode.js | 10 ++++++++++ src/expression/parse.js | 11 ++++++++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index d7f116bb45..28f5710a0b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -7,7 +7,7 @@ import log from 'fancy-log' import webpack from 'webpack' import babel from 'gulp-babel' import { mkdirp } from 'mkdirp' -import { cleanup, iteratePath } from './tools/docgenerator.js' +import { cleanup, iteratePath } from './tools/docgenerator.js' import { generateEntryFiles } from './tools/entryGenerator.js' import { getAllFiles, validateChars } from './tools/validateAsciiChars.js' diff --git a/src/expression/node/AccessorNode.js b/src/expression/node/AccessorNode.js index 8806699197..74d5fb6849 100644 --- a/src/expression/node/AccessorNode.js +++ b/src/expression/node/AccessorNode.js @@ -6,6 +6,7 @@ import { isIndexNode, isNode, isObjectNode, + isOperatorNode, isParenthesisNode, isSymbolNode } from '../../utils/is.js' @@ -93,6 +94,15 @@ export const createAccessorNode = /* #__PURE__ */ factory(name, dependencies, ({ const evalObject = this.object._compile(math, argNames) const evalIndex = this.index._compile(math, argNames) + // If index contains operator node, evaluate result of the operation and access object with result + if (isOperatorNode(this.index.dimensions[0]) && isObjectNode(this.object)) { + const operatorNode = this.index.dimensions[0] + return function evalAccessorNode (scope, args, context) { + const result = operatorNode._compile(math, argNames)(scope, args, context) + return getSafeProperty(evalObject(scope, args, context), String(result)) + } + } + if (this.index.isObjectProperty()) { const prop = this.index.getObjectProperty() return function evalAccessorNode (scope, args, context) { diff --git a/src/expression/parse.js b/src/expression/parse.js index b376025d87..10491a83d8 100644 --- a/src/expression/parse.js +++ b/src/expression/parse.js @@ -1,5 +1,5 @@ import { factory } from '../utils/factory.js' -import { isAccessorNode, isConstantNode, isFunctionNode, isOperatorNode, isSymbolNode, rule2Node } from '../utils/is.js' +import { isAccessorNode, isConstantNode, isFunctionNode, isObjectNode, isOperatorNode, isSymbolNode, rule2Node } from '../utils/is.js' import { deepMap } from '../utils/collection.js' import { safeNumberType } from '../utils/number.js' import { hasOwnProperty } from '../utils/object.js' @@ -1413,6 +1413,11 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({ closeParams(state) getToken(state) + // If param value is number and node is object node, make param value a string + if (typeof params[0].value === 'number' && isObjectNode(node) && params.length === 1 && isConstantNode(params[0])) { + params[0].value = String(params[0].value) + } + node = new AccessorNode(node, new IndexNode(params)) } else { // dot notation like variable.prop @@ -1615,11 +1620,11 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({ // parse key if (state.token === '"' || state.token === "'") { key = parseStringToken(state, state.token) - } else if (state.tokenType === TOKENTYPE.SYMBOL || (state.tokenType === TOKENTYPE.DELIMITER && state.token in NAMED_DELIMITERS)) { + } else if (state.tokenType === TOKENTYPE.SYMBOL || (state.tokenType === TOKENTYPE.DELIMITER && state.token in NAMED_DELIMITERS) || state.tokenType === TOKENTYPE.NUMBER) { key = state.token getToken(state) } else { - throw createSyntaxError(state, 'Symbol or string expected as object key') + throw createSyntaxError(state, 'Symbol, numeric literal or string expected as object key') } // parse key/value separator From 384e0fe00a5b19b0388fc51105bf88ec9bb965c7 Mon Sep 17 00:00:00 2001 From: Edward-Precious Omegbu Date: Mon, 30 Jun 2025 02:49:17 +0100 Subject: [PATCH 3/8] fix: number literals with preceeding zeros as object keys and accessors handled --- src/expression/parse.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/expression/parse.js b/src/expression/parse.js index 10491a83d8..cbbb1e3b2f 100644 --- a/src/expression/parse.js +++ b/src/expression/parse.js @@ -1415,7 +1415,8 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({ // If param value is number and node is object node, make param value a string if (typeof params[0].value === 'number' && isObjectNode(node) && params.length === 1 && isConstantNode(params[0])) { - params[0].value = String(params[0].value) + // Number constructor is first used to manage situations of numbers with preceding zero digit(s) + params[0].value = String(Number(params[0].value)) } node = new AccessorNode(node, new IndexNode(params)) @@ -1621,7 +1622,7 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({ if (state.token === '"' || state.token === "'") { key = parseStringToken(state, state.token) } else if (state.tokenType === TOKENTYPE.SYMBOL || (state.tokenType === TOKENTYPE.DELIMITER && state.token in NAMED_DELIMITERS) || state.tokenType === TOKENTYPE.NUMBER) { - key = state.token + key = state.tokenType === TOKENTYPE.NUMBER ? String(Number(state.token)) : state.token getToken(state) } else { throw createSyntaxError(state, 'Symbol, numeric literal or string expected as object key') From 90cb3f6d4012f4df733ca9c3fa8ca4fcc69b3afd Mon Sep 17 00:00:00 2001 From: Edward-Precious Omegbu Date: Tue, 1 Jul 2025 12:13:09 +0100 Subject: [PATCH 4/8] Revert "fix: eslint and prettier config updated to allow endOfLine as auto" This reverts commit cedc04f20afaa50168f1363e7b04f2e031ddc258. --- .eslintrc.cjs | 4 ---- .prettierrc.cjs | 3 +-- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index de186937d6..fb66ab5c16 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -43,10 +43,6 @@ module.exports = { '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' } - ], - 'prettier/prettier': [ - 'error', - { endOfLine: 'auto' } ] }, plugins: ['@typescript-eslint'], diff --git a/.prettierrc.cjs b/.prettierrc.cjs index d4ac8de058..2c815ce252 100644 --- a/.prettierrc.cjs +++ b/.prettierrc.cjs @@ -1,6 +1,5 @@ module.exports = { semi: false, singleQuote: true, - trailingComma: 'none', - endOfLine: 'auto' + trailingComma: 'none' } From 349f39d1efb3a2695926b0aa68d3e83672b7fb08 Mon Sep 17 00:00:00 2001 From: Edward-Precious Omegbu Date: Tue, 1 Jul 2025 17:09:22 +0100 Subject: [PATCH 5/8] revert: unnecessary change to gulpfile.js removed --- gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 28f5710a0b..d7f116bb45 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -7,7 +7,7 @@ import log from 'fancy-log' import webpack from 'webpack' import babel from 'gulp-babel' import { mkdirp } from 'mkdirp' -import { cleanup, iteratePath } from './tools/docgenerator.js' +import { cleanup, iteratePath } from './tools/docgenerator.js' import { generateEntryFiles } from './tools/entryGenerator.js' import { getAllFiles, validateChars } from './tools/validateAsciiChars.js' From eafba640f14184363985ea0ecdd1dd1aef5d35b9 Mon Sep 17 00:00:00 2001 From: Edward-Precious Omegbu Date: Tue, 1 Jul 2025 18:59:16 +0100 Subject: [PATCH 6/8] feat: added thorough suite of unit tests --- test/unit-tests/expression/parse.test.js | 36 ++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/unit-tests/expression/parse.test.js b/test/unit-tests/expression/parse.test.js index 09455c5dc0..f89eb83cf7 100644 --- a/test/unit-tests/expression/parse.test.js +++ b/test/unit-tests/expression/parse.test.js @@ -919,6 +919,42 @@ describe('parse', function () { }, /Cannot apply a numeric index as object property/) }) + it('should coerce numbers to string when trying to apply a numeric key in an object expression', function () { + assert.deepStrictEqual(parseAndEval('{2: 6}'), { 2: 6 }) + }) + + it('should throw an error when negative numbers are applied as keys in an object expression', function () { + assert.throws(function () { + parseAndEval('{-1: 34}') + }, /Symbol, numeric literal or string expected as object key \(char 2\)/) + }) + + it('should coerce numbers to string when trying to access an object expression property with matrix index', function () { + assert.strictEqual(parseAndEval('{2: 6}[2]'), 6) + assert.strictEqual(parseAndEval('{2.5: 6}[2.5]'), 6) + assert.strictEqual(parseAndEval('{5: 16}[3]'), undefined) + }) + + it('should accept operations in a matrix when trying to access an object expression property', function () { + assert.strictEqual(parseAndEval('{2: 6}[2 * 1]'), 6) + assert.strictEqual(parseAndEval('{31: 7 - 4}[0.2 + 0.8]'), undefined) + assert.strictEqual(parseAndEval('{4: 11 * 4}[(2 ^ 2) * 1]'), 44) + }) + + it('should ignore leading zeros when trying to apply numeric keys in an object expression', function () { + assert.deepStrictEqual(parseAndEval('{02: 6}'), { 2: 6 }) + assert.deepStrictEqual(parseAndEval('{0070: 6}'), { 70: 6 }) + assert.deepStrictEqual(parseAndEval('{0.2: 6}'), { 0.2: 6 }) + assert.deepStrictEqual(parseAndEval('{0010.0501: "haha"}'), { 10.0501: 'haha' }) + }) + + it('should ignore leading zeros in a matrix index when trying to access an object expression property', function () { + assert.strictEqual(parseAndEval('{2: 6}[02]'), 6) + assert.strictEqual(parseAndEval('{70: 1 - 6}[0070]'), -5) + assert.strictEqual(parseAndEval('{0.2: 6}[000.2]'), 6) + assert.strictEqual(parseAndEval('{10.0501: "haha"}[0010.0501]'), 'haha') + }) + it('should set a nested matrix subset from an object property (1)', function () { const scope = { obj: { foo: [1, 2, 3] } } assert.deepStrictEqual(parseAndEval('obj.foo[2] = 6', scope), 6) From f95f57f3ef5245a4bca8dd30db55fc89a1af0ba1 Mon Sep 17 00:00:00 2001 From: Edward-Precious Omegbu Date: Thu, 3 Jul 2025 13:42:55 +0100 Subject: [PATCH 7/8] fix: negative number object keys allowed and number object accessor logic moved to IndexNode --- src/expression/node/IndexNode.js | 11 +++++++---- src/expression/parse.js | 13 ++++++++----- test/unit-tests/expression/parse.test.js | 8 ++------ 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/expression/node/IndexNode.js b/src/expression/node/IndexNode.js index 8cd0fa2d93..e486c3f3f1 100644 --- a/src/expression/node/IndexNode.js +++ b/src/expression/node/IndexNode.js @@ -149,9 +149,12 @@ export const createIndexNode = /* #__PURE__ */ factory(name, dependencies, ({ No * @return {boolean} */ isObjectProperty () { - return this.dimensions.length === 1 && - isConstantNode(this.dimensions[0]) && - typeof this.dimensions[0].value === 'string' + if (this.dimensions.length !== 1) return false + + return isConstantNode(this.dimensions[0]) && + (typeof this.dimensions[0].value === 'string' || + (!!this.dimensions[0].forObjectNode && + typeof this.dimensions[0].value === 'number')) } /** @@ -160,7 +163,7 @@ export const createIndexNode = /* #__PURE__ */ factory(name, dependencies, ({ No * @return {string | null} */ getObjectProperty () { - return this.isObjectProperty() ? this.dimensions[0].value : null + return this.isObjectProperty() ? String(this.dimensions[0].value) : null } /** diff --git a/src/expression/parse.js b/src/expression/parse.js index cbbb1e3b2f..ca099f0910 100644 --- a/src/expression/parse.js +++ b/src/expression/parse.js @@ -1413,11 +1413,7 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({ closeParams(state) getToken(state) - // If param value is number and node is object node, make param value a string - if (typeof params[0].value === 'number' && isObjectNode(node) && params.length === 1 && isConstantNode(params[0])) { - // Number constructor is first used to manage situations of numbers with preceding zero digit(s) - params[0].value = String(Number(params[0].value)) - } + params[0].forObjectNode = isObjectNode(node) node = new AccessorNode(node, new IndexNode(params)) } else { @@ -1624,6 +1620,13 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({ } else if (state.tokenType === TOKENTYPE.SYMBOL || (state.tokenType === TOKENTYPE.DELIMITER && state.token in NAMED_DELIMITERS) || state.tokenType === TOKENTYPE.NUMBER) { key = state.tokenType === TOKENTYPE.NUMBER ? String(Number(state.token)) : state.token getToken(state) + } else if (state.token === '-') { + const minusNode = parseUnary(state) + if (isOperatorNode(minusNode) && minusNode.args.length === 1 && isConstantNode(minusNode.args[0]) && typeof minusNode.args[0].value === 'number') { + key = `-${minusNode.args[0].value}` + } else { + throw createSyntaxError(state, 'Numeric literal expected after "-" as object key') + } } else { throw createSyntaxError(state, 'Symbol, numeric literal or string expected as object key') } diff --git a/test/unit-tests/expression/parse.test.js b/test/unit-tests/expression/parse.test.js index f89eb83cf7..f6309cd2c3 100644 --- a/test/unit-tests/expression/parse.test.js +++ b/test/unit-tests/expression/parse.test.js @@ -921,12 +921,7 @@ describe('parse', function () { it('should coerce numbers to string when trying to apply a numeric key in an object expression', function () { assert.deepStrictEqual(parseAndEval('{2: 6}'), { 2: 6 }) - }) - - it('should throw an error when negative numbers are applied as keys in an object expression', function () { - assert.throws(function () { - parseAndEval('{-1: 34}') - }, /Symbol, numeric literal or string expected as object key \(char 2\)/) + assert.deepStrictEqual(parseAndEval('{-2: 6}'), { '-2': 6 }) }) it('should coerce numbers to string when trying to access an object expression property with matrix index', function () { @@ -939,6 +934,7 @@ describe('parse', function () { assert.strictEqual(parseAndEval('{2: 6}[2 * 1]'), 6) assert.strictEqual(parseAndEval('{31: 7 - 4}[0.2 + 0.8]'), undefined) assert.strictEqual(parseAndEval('{4: 11 * 4}[(2 ^ 2) * 1]'), 44) + assert.strictEqual(parseAndEval('{-4: 11 * 4}[-4]'), 44) }) it('should ignore leading zeros when trying to apply numeric keys in an object expression', function () { From 12b23bfb8d18133b1368e33121b67a2ef64feeec Mon Sep 17 00:00:00 2001 From: Edward-Precious Omegbu Date: Sat, 5 Jul 2025 18:58:43 +0100 Subject: [PATCH 8/8] draft: object assignment and accessing with matrix indexes accepts number operations/expressions --- src/expression/node/AccessorNode.js | 40 +++--------------------- src/expression/node/IndexNode.js | 11 +++---- src/expression/node/utils/access.js | 19 +++++++++-- src/expression/node/utils/assign.js | 23 +++++++++++--- src/expression/parse.js | 4 +-- test/unit-tests/expression/parse.test.js | 36 +++++++++++++++------ 6 files changed, 72 insertions(+), 61 deletions(-) diff --git a/src/expression/node/AccessorNode.js b/src/expression/node/AccessorNode.js index 74d5fb6849..aa4d592e94 100644 --- a/src/expression/node/AccessorNode.js +++ b/src/expression/node/AccessorNode.js @@ -6,11 +6,9 @@ import { isIndexNode, isNode, isObjectNode, - isOperatorNode, isParenthesisNode, isSymbolNode } from '../../utils/is.js' -import { getSafeProperty } from '../../utils/customs.js' import { factory } from '../../utils/factory.js' import { accessFactory } from './utils/access.js' @@ -62,17 +60,6 @@ export const createAccessorNode = /* #__PURE__ */ factory(name, dependencies, ({ this.index = index } - // readonly property name - get name () { - if (this.index) { - return (this.index.isObjectProperty()) - ? this.index.getObjectProperty() - : '' - } else { - return this.object.name || '' - } - } - static name = name get type () { return name } get isAccessorNode () { return true } @@ -94,28 +81,11 @@ export const createAccessorNode = /* #__PURE__ */ factory(name, dependencies, ({ const evalObject = this.object._compile(math, argNames) const evalIndex = this.index._compile(math, argNames) - // If index contains operator node, evaluate result of the operation and access object with result - if (isOperatorNode(this.index.dimensions[0]) && isObjectNode(this.object)) { - const operatorNode = this.index.dimensions[0] - return function evalAccessorNode (scope, args, context) { - const result = operatorNode._compile(math, argNames)(scope, args, context) - return getSafeProperty(evalObject(scope, args, context), String(result)) - } - } - - if (this.index.isObjectProperty()) { - const prop = this.index.getObjectProperty() - return function evalAccessorNode (scope, args, context) { - // get a property from an object evaluated using the scope. - return getSafeProperty(evalObject(scope, args, context), prop) - } - } else { - return function evalAccessorNode (scope, args, context) { - const object = evalObject(scope, args, context) - // we pass just object here instead of context: - const index = evalIndex(scope, args, object) - return access(object, index) - } + return function evalAccessorNode (scope, args, context) { + const object = evalObject(scope, args, context) + // we pass just object here instead of context: + const index = evalIndex(scope, args, object) + return access(object, index) } } diff --git a/src/expression/node/IndexNode.js b/src/expression/node/IndexNode.js index e486c3f3f1..8cd0fa2d93 100644 --- a/src/expression/node/IndexNode.js +++ b/src/expression/node/IndexNode.js @@ -149,12 +149,9 @@ export const createIndexNode = /* #__PURE__ */ factory(name, dependencies, ({ No * @return {boolean} */ isObjectProperty () { - if (this.dimensions.length !== 1) return false - - return isConstantNode(this.dimensions[0]) && - (typeof this.dimensions[0].value === 'string' || - (!!this.dimensions[0].forObjectNode && - typeof this.dimensions[0].value === 'number')) + return this.dimensions.length === 1 && + isConstantNode(this.dimensions[0]) && + typeof this.dimensions[0].value === 'string' } /** @@ -163,7 +160,7 @@ export const createIndexNode = /* #__PURE__ */ factory(name, dependencies, ({ No * @return {string | null} */ getObjectProperty () { - return this.isObjectProperty() ? String(this.dimensions[0].value) : null + return this.isObjectProperty() ? this.dimensions[0].value : null } /** diff --git a/src/expression/node/utils/access.js b/src/expression/node/utils/access.js index 5c87041965..0a5f71a6d6 100644 --- a/src/expression/node/utils/access.js +++ b/src/expression/node/utils/access.js @@ -1,5 +1,6 @@ import { errorTransform } from '../../transform/utils/errorTransform.js' import { getSafeProperty } from '../../../utils/customs.js' +import { isDenseMatrix } from '../../../utils/is.js' export function accessFactory ({ subset }) { /** @@ -23,11 +24,23 @@ export function accessFactory ({ subset }) { // TODO: move getStringSubset into a separate util file, use that return subset(object, index) } else if (typeof object === 'object') { - if (!index.isObjectProperty()) { - throw new TypeError('Cannot apply a numeric index as object property') + if (index.isObjectProperty()) { + return getSafeProperty(object, index.getObjectProperty()) } - return getSafeProperty(object, index.getObjectProperty()) + if (index._dimensions.length > 1) { + throw new SyntaxError('Cannot apply multi-element matrix as object property') + } + + if (isDenseMatrix(index._dimensions[0])) { + const compiledIndex = index._dimensions[0].get([0]) + + // For some reason, the value in the generated Dense Matrix _data + // is always 1 less than the expected calculated value + return getSafeProperty(object, String(compiledIndex + 1)) + } + + throw new TypeError('Cannot apply unsupported value as object property') } else { throw new TypeError('Cannot apply index: unsupported type of object') } diff --git a/src/expression/node/utils/assign.js b/src/expression/node/utils/assign.js index 6e1b59b833..fdafa79599 100644 --- a/src/expression/node/utils/assign.js +++ b/src/expression/node/utils/assign.js @@ -1,5 +1,6 @@ import { errorTransform } from '../../transform/utils/errorTransform.js' import { setSafeProperty } from '../../../utils/customs.js' +import { isDenseMatrix } from '../../../utils/is.js' export function assignFactory ({ subset, matrix }) { /** @@ -33,11 +34,25 @@ export function assignFactory ({ subset, matrix }) { // TODO: move setStringSubset into a separate util file, use that return subset(object, index, value) } else if (typeof object === 'object') { - if (!index.isObjectProperty()) { - throw TypeError('Cannot apply a numeric index as object property') + if (index.isObjectProperty()) { + setSafeProperty(object, index.getObjectProperty(), value) + return object } - setSafeProperty(object, index.getObjectProperty(), value) - return object + + if (index._dimensions.length > 1) { + throw new SyntaxError('Cannot apply multi-element matrix as object property') + } + + if (isDenseMatrix(index._dimensions[0])) { + const compiledIndex = index._dimensions[0].get([0]) + + // For some reason, the value in the generated Dense Matrix _data + // is always 1 less than the expected calculated value + setSafeProperty(object, String(compiledIndex + 1), value) + return object + } + + throw new TypeError('Cannot apply unsupported value as object property') } else { throw new TypeError('Cannot apply index: unsupported type of object') } diff --git a/src/expression/parse.js b/src/expression/parse.js index ca099f0910..3b130b1f4c 100644 --- a/src/expression/parse.js +++ b/src/expression/parse.js @@ -1,5 +1,5 @@ import { factory } from '../utils/factory.js' -import { isAccessorNode, isConstantNode, isFunctionNode, isObjectNode, isOperatorNode, isSymbolNode, rule2Node } from '../utils/is.js' +import { isAccessorNode, isConstantNode, isFunctionNode, isOperatorNode, isSymbolNode, rule2Node } from '../utils/is.js' import { deepMap } from '../utils/collection.js' import { safeNumberType } from '../utils/number.js' import { hasOwnProperty } from '../utils/object.js' @@ -1413,8 +1413,6 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({ closeParams(state) getToken(state) - params[0].forObjectNode = isObjectNode(node) - node = new AccessorNode(node, new IndexNode(params)) } else { // dot notation like variable.prop diff --git a/test/unit-tests/expression/parse.test.js b/test/unit-tests/expression/parse.test.js index f6309cd2c3..db84f0d029 100644 --- a/test/unit-tests/expression/parse.test.js +++ b/test/unit-tests/expression/parse.test.js @@ -912,13 +912,6 @@ describe('parse', function () { assert.deepStrictEqual(scope, { obj: { foo: { bar: 2 } } }) }) - it('should throw an error when trying to apply a matrix index as object property', function () { - const scope = { a: {} } - assert.throws(function () { - parseAndEval('a[2] = 6', scope) - }, /Cannot apply a numeric index as object property/) - }) - it('should coerce numbers to string when trying to apply a numeric key in an object expression', function () { assert.deepStrictEqual(parseAndEval('{2: 6}'), { 2: 6 }) assert.deepStrictEqual(parseAndEval('{-2: 6}'), { '-2': 6 }) @@ -930,13 +923,31 @@ describe('parse', function () { assert.strictEqual(parseAndEval('{5: 16}[3]'), undefined) }) - it('should accept operations in a matrix when trying to access an object expression property', function () { + it('should coerce numbers to string when trying to set an object property with matrix index', function () { + const scope = { obj: {} } + const res1 = parseAndEval('obj[2] = 6', scope) + const res2 = parseAndEval('obj[-2.5] = {4: "haha"}', scope) + assert.strictEqual(res1, 6) + assert.strictEqual(res2, { 4: 'haha' }) + assert.deepStrictEqual(scope, { obj: { 2: 6, '-2.5': { 4: 'haha' } } }) + }) + + it('should accept operations / expressions in a matrix when trying to access an object expression property', function () { assert.strictEqual(parseAndEval('{2: 6}[2 * 1]'), 6) assert.strictEqual(parseAndEval('{31: 7 - 4}[0.2 + 0.8]'), undefined) - assert.strictEqual(parseAndEval('{4: 11 * 4}[(2 ^ 2) * 1]'), 44) + assert.strictEqual(parseAndEval('{6: "haha"}[multiply(2, 3)]'), 'haha') assert.strictEqual(parseAndEval('{-4: 11 * 4}[-4]'), 44) }) + it('should accept operations / expressions in a matrix when trying to set an object property', function () { + const scope = { obj: {} } + const res1 = parseAndEval('obj[2^2] = 6', scope) + const res2 = parseAndEval('obj[multiply(2, add(3,1))] = "haha"', scope) + assert.strictEqual(res1, 6) + assert.strictEqual(res2, 'haha') + assert.deepStrictEqual(scope, { obj: { 4: 6, 8: 'haha' } }) + }) + it('should ignore leading zeros when trying to apply numeric keys in an object expression', function () { assert.deepStrictEqual(parseAndEval('{02: 6}'), { 2: 6 }) assert.deepStrictEqual(parseAndEval('{0070: 6}'), { 70: 6 }) @@ -951,6 +962,13 @@ describe('parse', function () { assert.strictEqual(parseAndEval('{10.0501: "haha"}[0010.0501]'), 'haha') }) + it('should ignore leading zeros in a matrix index when trying to set an object property', function () { + const scope = { obj: {} } + const res = parseAndEval('obj[02] = 6', scope) + assert.strictEqual(res, 6) + assert.deepStrictEqual(scope, { obj: { 2: 6 } }) + }) + it('should set a nested matrix subset from an object property (1)', function () { const scope = { obj: { foo: [1, 2, 3] } } assert.deepStrictEqual(parseAndEval('obj.foo[2] = 6', scope), 6)