From 4bca2126af51344993a7e707c6a1af3adebf5a9a Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 31 Dec 2021 02:45:04 +0000 Subject: [PATCH 01/33] feat: Implemented new binding expression parser for NativeScript Core. --- packages/core/js-libs/esprima/LICENSE.BSD | 19 - packages/core/js-libs/esprima/README.md | 24 - packages/core/js-libs/esprima/esprima.d.ts | 266 ----- packages/core/js-libs/esprima/esprima.js | 1042 ----------------- packages/core/js-libs/esprima/package.json | 75 -- .../core/js-libs/polymer-expressions/LICENSE | 27 - .../js-libs/polymer-expressions/README.md | 0 .../js-libs/polymer-expressions/package.json | 6 - .../polymer-expressions/path-parser.js | 393 ------- .../polymer-expressions.d.ts | 14 - .../polymer-expressions.js | 541 --------- packages/core/package.json | 47 +- .../ui/core/bindable/bindable-expressions.ts | 167 +++ packages/core/ui/core/bindable/index.ts | 15 +- 14 files changed, 200 insertions(+), 2436 deletions(-) delete mode 100644 packages/core/js-libs/esprima/LICENSE.BSD delete mode 100644 packages/core/js-libs/esprima/README.md delete mode 100644 packages/core/js-libs/esprima/esprima.d.ts delete mode 100644 packages/core/js-libs/esprima/esprima.js delete mode 100644 packages/core/js-libs/esprima/package.json delete mode 100644 packages/core/js-libs/polymer-expressions/LICENSE delete mode 100644 packages/core/js-libs/polymer-expressions/README.md delete mode 100644 packages/core/js-libs/polymer-expressions/package.json delete mode 100644 packages/core/js-libs/polymer-expressions/path-parser.js delete mode 100644 packages/core/js-libs/polymer-expressions/polymer-expressions.d.ts delete mode 100644 packages/core/js-libs/polymer-expressions/polymer-expressions.js create mode 100644 packages/core/ui/core/bindable/bindable-expressions.ts diff --git a/packages/core/js-libs/esprima/LICENSE.BSD b/packages/core/js-libs/esprima/LICENSE.BSD deleted file mode 100644 index 3e580c355a..0000000000 --- a/packages/core/js-libs/esprima/LICENSE.BSD +++ /dev/null @@ -1,19 +0,0 @@ -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/core/js-libs/esprima/README.md b/packages/core/js-libs/esprima/README.md deleted file mode 100644 index 0ccc1fa7cd..0000000000 --- a/packages/core/js-libs/esprima/README.md +++ /dev/null @@ -1,24 +0,0 @@ -**Esprima** ([esprima.org](http://esprima.org), BSD license) is a high performance, -standard-compliant [ECMAScript](http://www.ecma-international.org/publications/standards/Ecma-262.htm) -parser written in ECMAScript (also popularly known as -[JavaScript](http://en.wikipedia.org/wiki/JavaScript). -Esprima is created and maintained by [Ariya Hidayat](http://twitter.com/ariyahidayat), -with the help of [many contributors](https://github.com/ariya/esprima/contributors). - -### Features - -- Full support for ECMAScript 5.1 ([ECMA-262](http://www.ecma-international.org/publications/standards/Ecma-262.htm)) -- Sensible [syntax tree format](http://esprima.org/doc/index.html#ast) compatible with Mozilla -[Parser AST](https://web.archive.org/web/20201119095346/https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API) -- Optional tracking of syntax node location (index-based and line-column) -- Heavily tested (> 700 [unit tests](http://esprima.org/test/) with [full code coverage](http://esprima.org/test/coverage.html)) -- [Partial support](http://esprima.org/doc/es6.html) for ECMAScript 6 - -Esprima serves as a **building block** for some JavaScript -language tools, from [code instrumentation](http://esprima.org/demo/functiontrace.html) -to [editor autocompletion](http://esprima.org/demo/autocomplete.html). - -Esprima runs on many popular web browsers, as well as other ECMAScript platforms such as -[Rhino](https://github.com/mozilla/rhino), [Nashorn](http://openjdk.java.net/projects/nashorn/), and [Node.js](https://npmjs.org/package/esprima). - -For more information, check the web site [esprima.org](http://esprima.org). diff --git a/packages/core/js-libs/esprima/esprima.d.ts b/packages/core/js-libs/esprima/esprima.d.ts deleted file mode 100644 index 69dffe728a..0000000000 --- a/packages/core/js-libs/esprima/esprima.d.ts +++ /dev/null @@ -1,266 +0,0 @@ -/* tslint:disable */ - -// Type definitions for Esprima v1.2.0 -// Project: http://esprima.org -// Definitions by: teppeis -// Definitions: https://github.com/borisyankov/DefinitelyTyped - -export const version: string; -export function parse(code: string, options?: Options): Syntax.Program; -export function tokenize(code: string, options?: Options): Array; - -export interface Token { - type: string - value: string -} - -export interface Options { - loc?: boolean - range?: boolean - raw?: boolean - tokens?: boolean - comment?: boolean - attachComment?: boolean - tolerant?: boolean - source?: boolean -} - -export namespace Syntax { - // Node - interface Node { - type: string - loc?: LineLocation - range?: number[] - leadingComments?: Comment[] - trailingComments?: Comment[] - } - interface LineLocation { - start: Position - end: Position - } - interface Position { - line: number - column: number - } - - // Comment - interface Comment extends Node { - value: string - } - - // Program - interface Program extends Node { - body: SomeStatement[] - comments?: Comment[] - } - - // Function - interface Function extends Node { - id: Identifier // | null - params: Identifier[] - defaults: SomeExpression[] - rest: Identifier // | null - body: BlockStatementOrExpression - generator: boolean - expression: boolean - } - interface BlockStatementOrExpression extends Array, BlockStatement, SomeExpression { - body: BlockStatementOrExpression - } - - // Statement - type Statement = Node - type EmptyStatement = Statement - interface BlockStatement extends Statement { - body: SomeStatement[] - } - interface ExpressionStatement extends Statement { - expression: SomeExpression - } - interface IfStatement extends Statement { - test: SomeExpression - consequent: SomeStatement - alternate: SomeStatement - } - interface LabeledStatement extends Statement { - label: Identifier - body: SomeStatement - } - interface BreakStatement extends Statement { - label: Identifier // | null - } - interface ContinueStatement extends Statement { - label: Identifier // | null - } - interface WithStatement extends Statement { - object: SomeExpression - body: SomeStatement - } - interface SwitchStatement extends Statement { - discriminant: SomeExpression - cases: SwitchCase[] - lexical: boolean - } - interface ReturnStatement extends Statement { - argument: SomeExpression // | null - } - interface ThrowStatement extends Statement { - argument: SomeExpression - } - interface TryStatement extends Statement { - block: BlockStatement - handler: CatchClause // | null - guardedHandlers: CatchClause[] - finalizer: BlockStatement // | null - } - interface WhileStatement extends Statement { - test: SomeExpression - body: SomeStatement - } - interface DoWhileStatement extends Statement { - body: SomeStatement - test: SomeExpression - } - interface ForStatement extends Statement { - init: VariableDeclaratorOrExpression // | null - test: SomeExpression // | null - update: SomeExpression // | null - body: SomeStatement - } - interface ForInStatement extends Statement { - left: VariableDeclaratorOrExpression - right: SomeExpression - body: SomeStatement - each: boolean - } - interface VariableDeclaratorOrExpression extends VariableDeclarator, SomeExpression { - } - type DebuggerStatement = Statement - interface SomeStatement extends - EmptyStatement, ExpressionStatement, BlockStatement, IfStatement, - LabeledStatement, BreakStatement, ContinueStatement, WithStatement, - SwitchStatement, ReturnStatement, ThrowStatement, TryStatement, - WhileStatement, DoWhileStatement, ForStatement, ForInStatement, DebuggerStatement { - body: SomeStatementOrList - } - interface SomeStatementOrList extends Array, SomeStatement { - } - - // Declration - type Declration = Statement - interface FunctionDeclration extends Declration { - id: Identifier - params: Identifier[] // Pattern - defaults: SomeExpression[] - rest: Identifier - body: BlockStatementOrExpression - generator: boolean - expression: boolean - } - interface VariableDeclaration extends Declration { - declarations: VariableDeclarator[] - kind: string // "var" | "let" | "const" - } - interface VariableDeclarator extends Node { - id: Identifier // Pattern - init: SomeExpression - } - - // Expression - type Expression = Node - interface SomeExpression extends - ThisExpression, ArrayExpression, ObjectExpression, FunctionExpression, - ArrowFunctionExpression, SequenceExpression, UnaryExpression, BinaryExpression, - AssignmentExpression, UpdateExpression, LogicalExpression, ConditionalExpression, - NewExpression, CallExpression, MemberExpression { - } - type ThisExpression = Expression - interface ArrayExpression extends Expression { - elements: SomeExpression[] // [ Expression | null ] - } - interface ObjectExpression extends Expression { - properties: Property[] - } - interface Property extends Node { - key: LiteralOrIdentifier // Literal | Identifier - value: SomeExpression - kind: string // "init" | "get" | "set" - } - interface LiteralOrIdentifier extends Literal, Identifier { - } - interface FunctionExpression extends Function, Expression { - } - interface ArrowFunctionExpression extends Function, Expression { - } - interface SequenceExpression extends Expression { - expressions: SomeExpression[] - } - interface UnaryExpression extends Expression { - operator: string // UnaryOperator - prefix: boolean - argument: SomeExpression - } - interface BinaryExpression extends Expression { - operator: string // BinaryOperator - left: SomeExpression - right: SomeExpression - } - interface AssignmentExpression extends Expression { - operator: string // AssignmentOperator - left: SomeExpression - right: SomeExpression - } - interface UpdateExpression extends Expression { - operator: string // UpdateOperator - argument: SomeExpression - prefix: boolean - } - interface LogicalExpression extends Expression { - operator: string // LogicalOperator - left: SomeExpression - right: SomeExpression - } - interface ConditionalExpression extends Expression { - test: SomeExpression - alternate: SomeExpression - consequent: SomeExpression - } - interface NewExpression extends Expression { - callee: SomeExpression - arguments: SomeExpression[] - } - interface CallExpression extends Expression { - callee: SomeExpression - arguments: SomeExpression[] - } - interface MemberExpression extends Expression { - object: SomeExpression - property: IdentifierOrExpression // Identifier | Expression - computed: boolean - } - interface IdentifierOrExpression extends Identifier, SomeExpression { - } - - // Pattern - // interface Pattern extends Node { - // } - - // Clauses - interface SwitchCase extends Node { - test: SomeExpression - consequent: SomeStatement[] - } - interface CatchClause extends Node { - param: Identifier // Pattern - guard: SomeExpression - body: BlockStatement - } - - // Misc - interface Identifier extends Node, Expression { // | Pattern - name: string - } - interface Literal extends Node, Expression { - value: any // string | boolean | null | number | RegExp - } -} diff --git a/packages/core/js-libs/esprima/esprima.js b/packages/core/js-libs/esprima/esprima.js deleted file mode 100644 index d509cff068..0000000000 --- a/packages/core/js-libs/esprima/esprima.js +++ /dev/null @@ -1,1042 +0,0 @@ -/* - Copyright (C) 2013 Ariya Hidayat - Copyright (C) 2013 Thaddee Tyl - Copyright (C) 2012 Ariya Hidayat - Copyright (C) 2012 Mathias Bynens - Copyright (C) 2012 Joost-Wim Boekesteijn - Copyright (C) 2012 Kris Kowal - Copyright (C) 2012 Yusuke Suzuki - Copyright (C) 2012 Arpad Borsos - Copyright (C) 2011 Ariya Hidayat - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -(function (global) { - 'use strict'; - - var Token, - TokenName, - Syntax, - Messages, - source, - index, - length, - delegate, - lookahead, - state; - - Token = { - BooleanLiteral: 1, - EOF: 2, - Identifier: 3, - Keyword: 4, - NullLiteral: 5, - NumericLiteral: 6, - Punctuator: 7, - StringLiteral: 8 - }; - - TokenName = {}; - TokenName[Token.BooleanLiteral] = 'Boolean'; - TokenName[Token.EOF] = ''; - TokenName[Token.Identifier] = 'Identifier'; - TokenName[Token.Keyword] = 'Keyword'; - TokenName[Token.NullLiteral] = 'Null'; - TokenName[Token.NumericLiteral] = 'Numeric'; - TokenName[Token.Punctuator] = 'Punctuator'; - TokenName[Token.StringLiteral] = 'String'; - - Syntax = { - ArrayExpression: 'ArrayExpression', - BinaryExpression: 'BinaryExpression', - CallExpression: 'CallExpression', - ConditionalExpression: 'ConditionalExpression', - EmptyStatement: 'EmptyStatement', - ExpressionStatement: 'ExpressionStatement', - Identifier: 'Identifier', - Literal: 'Literal', - LabeledStatement: 'LabeledStatement', - LogicalExpression: 'LogicalExpression', - MemberExpression: 'MemberExpression', - ObjectExpression: 'ObjectExpression', - Program: 'Program', - Property: 'Property', - ThisExpression: 'ThisExpression', - UnaryExpression: 'UnaryExpression' - }; - - // Error messages should be identical to V8. - Messages = { - UnexpectedToken: 'Unexpected token %0', - UnknownLabel: 'Undefined label \'%0\'', - Redeclaration: '%0 \'%1\' has already been declared' - }; - - // Ensure the condition is true, otherwise throw an error. - // This is only to have a better contract semantic, i.e. another safety net - // to catch a logic error. The condition shall be fulfilled in normal case. - // Do NOT use this to enforce a certain condition on any user input. - - function assert(condition, message) { - if (!condition) { - throw new Error('ASSERT: ' + message); - } - } - - function isDecimalDigit(ch) { - return (ch >= 48 && ch <= 57); // 0..9 - } - - - // 7.2 White Space - - function isWhiteSpace(ch) { - return (ch === 32) || // space - (ch === 9) || // tab - (ch === 0xB) || - (ch === 0xC) || - (ch === 0xA0) || - (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCode(ch)) > 0); - } - - // 7.3 Line Terminators - - function isLineTerminator(ch) { - return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029); - } - - // 7.6 Identifier Names and Identifiers - - function isIdentifierStart(ch) { - return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) - (ch >= 65 && ch <= 90) || // A..Z - (ch >= 97 && ch <= 122); // a..z - } - - function isIdentifierPart(ch) { - return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) - (ch >= 65 && ch <= 90) || // A..Z - (ch >= 97 && ch <= 122) || // a..z - (ch >= 48 && ch <= 57); // 0..9 - } - - // 7.6.1.1 Keywords - - function isKeyword(id) { - return (id === 'this') - } - - // 7.4 Comments - - function skipWhitespace() { - while (index < length && isWhiteSpace(source.charCodeAt(index))) { - ++index; - } - } - - function getIdentifier() { - var start, ch; - - start = index++; - while (index < length) { - ch = source.charCodeAt(index); - if (isIdentifierPart(ch)) { - ++index; - } else { - break; - } - } - - return source.slice(start, index); - } - - function scanIdentifier() { - var start, id, type; - - start = index; - - id = getIdentifier(); - - // There is no keyword or literal with only one character. - // Thus, it must be an identifier. - if (id.length === 1) { - type = Token.Identifier; - } else if (isKeyword(id)) { - type = Token.Keyword; - } else if (id === 'null') { - type = Token.NullLiteral; - } else if (id === 'true' || id === 'false') { - type = Token.BooleanLiteral; - } else { - type = Token.Identifier; - } - - return { - type: type, - value: id, - range: [start, index] - }; - } - - - // 7.7 Punctuators - - function scanPunctuator() { - var start = index, - code = source.charCodeAt(index), - code2, - ch1 = source[index], - ch2; - - switch (code) { - - // Check for most common single-character punctuators. - case 46: // . dot - case 40: // ( open bracket - case 41: // ) close bracket - case 59: // ; semicolon - case 44: // , comma - case 123: // { open curly brace - case 125: // } close curly brace - case 91: // [ - case 93: // ] - case 58: // : - case 63: // ? - ++index; - return { - type: Token.Punctuator, - value: String.fromCharCode(code), - range: [start, index] - }; - - default: - code2 = source.charCodeAt(index + 1); - - // '=' (char #61) marks an assignment or comparison operator. - if (code2 === 61) { - switch (code) { - case 37: // % - case 38: // & - case 42: // *: - case 43: // + - case 45: // - - case 47: // / - case 60: // < - case 62: // > - case 124: // | - index += 2; - return { - type: Token.Punctuator, - value: String.fromCharCode(code) + String.fromCharCode(code2), - range: [start, index] - }; - - case 33: // ! - case 61: // = - index += 2; - - // !== and === - if (source.charCodeAt(index) === 61) { - ++index; - } - return { - type: Token.Punctuator, - value: source.slice(start, index), - range: [start, index] - }; - default: - break; - } - } - break; - } - - // Peek more characters. - - ch2 = source[index + 1]; - - // Other 2-character punctuators: && || - - if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) { - index += 2; - return { - type: Token.Punctuator, - value: ch1 + ch2, - range: [start, index] - }; - } - - if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) { - ++index; - return { - type: Token.Punctuator, - value: ch1, - range: [start, index] - }; - } - - throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); - } - - // 7.8.3 Numeric Literals - function scanNumericLiteral() { - var number, start, ch; - - ch = source[index]; - assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'), - 'Numeric literal must start with a decimal digit or a decimal point'); - - start = index; - number = ''; - if (ch !== '.') { - number = source[index++]; - ch = source[index]; - - // Hex number starts with '0x'. - // Octal number starts with '0'. - if (number === '0') { - // decimal number starts with '0' such as '09' is illegal. - if (ch && isDecimalDigit(ch.charCodeAt(0))) { - throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); - } - } - - while (isDecimalDigit(source.charCodeAt(index))) { - number += source[index++]; - } - ch = source[index]; - } - - if (ch === '.') { - number += source[index++]; - while (isDecimalDigit(source.charCodeAt(index))) { - number += source[index++]; - } - ch = source[index]; - } - - if (ch === 'e' || ch === 'E') { - number += source[index++]; - - ch = source[index]; - if (ch === '+' || ch === '-') { - number += source[index++]; - } - if (isDecimalDigit(source.charCodeAt(index))) { - while (isDecimalDigit(source.charCodeAt(index))) { - number += source[index++]; - } - } else { - throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); - } - } - - if (isIdentifierStart(source.charCodeAt(index))) { - throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); - } - - return { - type: Token.NumericLiteral, - value: parseFloat(number), - range: [start, index] - }; - } - - // 7.8.4 String Literals - - function scanStringLiteral() { - var str = '', quote, start, ch, octal = false; - - quote = source[index]; - assert((quote === '\'' || quote === '"'), - 'String literal must starts with a quote'); - - start = index; - ++index; - - while (index < length) { - ch = source[index++]; - - if (ch === quote) { - quote = ''; - break; - } else if (ch === '\\') { - ch = source[index++]; - if (!ch || !isLineTerminator(ch.charCodeAt(0))) { - switch (ch) { - case 'n': - str += '\n'; - break; - case 'r': - str += '\r'; - break; - case 't': - str += '\t'; - break; - case 'b': - str += '\b'; - break; - case 'f': - str += '\f'; - break; - case 'v': - str += '\x0B'; - break; - - default: - str += ch; - break; - } - } else { - if (ch === '\r' && source[index] === '\n') { - ++index; - } - } - } else if (isLineTerminator(ch.charCodeAt(0))) { - break; - } else { - str += ch; - } - } - - if (quote !== '') { - throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); - } - - return { - type: Token.StringLiteral, - value: str, - octal: octal, - range: [start, index] - }; - } - - function isIdentifierName(token) { - return token.type === Token.Identifier || - token.type === Token.Keyword || - token.type === Token.BooleanLiteral || - token.type === Token.NullLiteral; - } - - function advance() { - var ch; - - skipWhitespace(); - - if (index >= length) { - return { - type: Token.EOF, - range: [index, index] - }; - } - - ch = source.charCodeAt(index); - - // Very common: ( and ) and ; - if (ch === 40 || ch === 41 || ch === 58) { - return scanPunctuator(); - } - - // String literal starts with single quote (#39) or double quote (#34). - if (ch === 39 || ch === 34) { - return scanStringLiteral(); - } - - if (isIdentifierStart(ch)) { - return scanIdentifier(); - } - - // Dot (.) char #46 can also start a floating-point number, hence the need - // to check the next character. - if (ch === 46) { - if (isDecimalDigit(source.charCodeAt(index + 1))) { - return scanNumericLiteral(); - } - return scanPunctuator(); - } - - if (isDecimalDigit(ch)) { - return scanNumericLiteral(); - } - - return scanPunctuator(); - } - - function lex() { - var token; - - token = lookahead; - index = token.range[1]; - - lookahead = advance(); - - index = token.range[1]; - - return token; - } - - function peek() { - var pos; - - pos = index; - lookahead = advance(); - index = pos; - } - - // Throw an exception - - function throwError(token, messageFormat) { - var error, - args = Array.prototype.slice.call(arguments, 2), - msg = messageFormat.replace( - /%(\d)/g, - function (whole, index) { - assert(index < args.length, 'Message reference must be in range'); - return args[index]; - } - ); - - error = new Error(msg); - error.index = index; - error.description = msg; - throw error; - } - - // Throw an exception because of the token. - - function throwUnexpected(token) { - throwError(token, Messages.UnexpectedToken, token.value); - } - - // Expect the next token to match the specified punctuator. - // If not, an exception will be thrown. - - function expect(value) { - var token = lex(); - if (token.type !== Token.Punctuator || token.value !== value) { - throwUnexpected(token); - } - } - - // Return true if the next token matches the specified punctuator. - - function match(value) { - return lookahead.type === Token.Punctuator && lookahead.value === value; - } - - // Return true if the next token matches the specified keyword - - function matchKeyword(keyword) { - return lookahead.type === Token.Keyword && lookahead.value === keyword; - } - - function consumeSemicolon() { - // Catch the very common case first: immediately a semicolon (char #59). - if (source.charCodeAt(index) === 59) { - lex(); - return; - } - - skipWhitespace(); - - if (match(';')) { - lex(); - return; - } - - if (lookahead.type !== Token.EOF && !match('}')) { - throwUnexpected(lookahead); - } - } - - // 11.1.4 Array Initialiser - - function parseArrayInitialiser() { - var elements = []; - - expect('['); - - while (!match(']')) { - if (match(',')) { - lex(); - elements.push(null); - } else { - elements.push(parseExpression()); - - if (!match(']')) { - expect(','); - } - } - } - - expect(']'); - - return delegate.createArrayExpression(elements); - } - - // 11.1.5 Object Initialiser - - function parseObjectPropertyKey() { - var token; - - skipWhitespace(); - token = lex(); - - // Note: This function is called only from parseObjectProperty(), where - // EOF and Punctuator tokens are already filtered out. - if (token.type === Token.StringLiteral || token.type === Token.NumericLiteral) { - return delegate.createLiteral(token); - } - - return delegate.createIdentifier(token.value); - } - - function parseObjectProperty() { - var token, key; - - token = lookahead; - skipWhitespace(); - - if (token.type === Token.EOF || token.type === Token.Punctuator) { - throwUnexpected(token); - } - - key = parseObjectPropertyKey(); - expect(':'); - return delegate.createProperty('init', key, parseExpression()); - } - - function parseObjectInitialiser() { - var properties = []; - - expect('{'); - - while (!match('}')) { - properties.push(parseObjectProperty()); - - if (!match('}')) { - expect(','); - } - } - - expect('}'); - - return delegate.createObjectExpression(properties); - } - - // 11.1.6 The Grouping Operator - - function parseGroupExpression() { - var expr; - - expect('('); - - expr = parseExpression(); - - expect(')'); - - return expr; - } - - - // 11.1 Primary Expressions - - function parsePrimaryExpression() { - var type, token, expr; - - if (match('(')) { - return parseGroupExpression(); - } - - type = lookahead.type; - - if (type === Token.Identifier) { - expr = delegate.createIdentifier(lex().value); - } else if (type === Token.StringLiteral || type === Token.NumericLiteral) { - expr = delegate.createLiteral(lex()); - } else if (type === Token.Keyword) { - if (matchKeyword('this')) { - lex(); - expr = delegate.createThisExpression(); - } - } else if (type === Token.BooleanLiteral) { - token = lex(); - token.value = (token.value === 'true'); - expr = delegate.createLiteral(token); - } else if (type === Token.NullLiteral) { - token = lex(); - token.value = null; - expr = delegate.createLiteral(token); - } else if (match('[')) { - expr = parseArrayInitialiser(); - } else if (match('{')) { - expr = parseObjectInitialiser(); - } - - if (expr) { - return expr; - } - - throwUnexpected(lex()); - } - - // 11.2 Left-Hand-Side Expressions - - function parseArguments() { - var args = []; - - expect('('); - - if (!match(')')) { - while (index < length) { - args.push(parseExpression()); - if (match(')')) { - break; - } - expect(','); - } - } - - expect(')'); - - return args; - } - - function parseNonComputedProperty() { - var token; - - token = lex(); - - if (!isIdentifierName(token)) { - throwUnexpected(token); - } - - return delegate.createIdentifier(token.value); - } - - function parseNonComputedMember() { - expect('.'); - - return parseNonComputedProperty(); - } - - function parseComputedMember() { - var expr; - - expect('['); - - expr = parseExpression(); - - expect(']'); - - return expr; - } - - function parseLeftHandSideExpression() { - var expr, args, property; - - expr = parsePrimaryExpression(); - - while (true) { - if (match('[')) { - property = parseComputedMember(); - expr = delegate.createMemberExpression('[', expr, property); - } else if (match('.')) { - property = parseNonComputedMember(); - expr = delegate.createMemberExpression('.', expr, property); - } else if (match('(')) { - args = parseArguments(); - expr = delegate.createCallExpression(expr, args); - } else { - break; - } - } - - return expr; - } - - // 11.3 Postfix Expressions - - var parsePostfixExpression = parseLeftHandSideExpression; - - // 11.4 Unary Operators - - function parseUnaryExpression() { - var token, expr; - - if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyword) { - expr = parsePostfixExpression(); - } else if (match('+') || match('-') || match('!')) { - token = lex(); - expr = parseUnaryExpression(); - expr = delegate.createUnaryExpression(token.value, expr); - } else if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) { - throwError({}, Messages.UnexpectedToken); - } else { - expr = parsePostfixExpression(); - } - - return expr; - } - - function binaryPrecedence(token) { - var prec = 0; - - if (token.type !== Token.Punctuator && token.type !== Token.Keyword) { - return 0; - } - - switch (token.value) { - case '||': - prec = 1; - break; - - case '&&': - prec = 2; - break; - - case '==': - case '!=': - case '===': - case '!==': - prec = 6; - break; - - case '<': - case '>': - case '<=': - case '>=': - case 'instanceof': - prec = 7; - break; - - case 'in': - prec = 7; - break; - - case '+': - case '-': - prec = 9; - break; - - case '*': - case '/': - case '%': - prec = 11; - break; - - default: - break; - } - - return prec; - } - - // 11.5 Multiplicative Operators - // 11.6 Additive Operators - // 11.7 Bitwise Shift Operators - // 11.8 Relational Operators - // 11.9 Equality Operators - // 11.10 Binary Bitwise Operators - // 11.11 Binary Logical Operators - - function parseBinaryExpression() { - var expr, token, prec, stack, right, operator, left, i; - - left = parseUnaryExpression(); - - token = lookahead; - prec = binaryPrecedence(token); - if (prec === 0) { - return left; - } - token.prec = prec; - lex(); - - right = parseUnaryExpression(); - - stack = [left, token, right]; - - while ((prec = binaryPrecedence(lookahead)) > 0) { - - // Reduce: make a binary expression from the three topmost entries. - while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) { - right = stack.pop(); - operator = stack.pop().value; - left = stack.pop(); - expr = delegate.createBinaryExpression(operator, left, right); - stack.push(expr); - } - - // Shift. - token = lex(); - token.prec = prec; - stack.push(token); - expr = parseUnaryExpression(); - stack.push(expr); - } - - // Final reduce to clean-up the stack. - i = stack.length - 1; - expr = stack[i]; - while (i > 1) { - expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i - 2], expr); - i -= 2; - } - - return expr; - } - - - // 11.12 Conditional Operator - - function parseConditionalExpression() { - var expr, consequent, alternate; - - expr = parseBinaryExpression(); - - if (match('?')) { - lex(); - consequent = parseConditionalExpression(); - expect(':'); - alternate = parseConditionalExpression(); - - expr = delegate.createConditionalExpression(expr, consequent, alternate); - } - - return expr; - } - - // Simplification since we do not support AssignmentExpression. - var parseExpression = parseConditionalExpression; - - // Polymer Syntax extensions - - // Filter :: - // Identifier - // Identifier "(" ")" - // Identifier "(" FilterArguments ")" - - function parseFilter() { - var identifier, args; - - identifier = lex(); - - if (identifier.type !== Token.Identifier) { - throwUnexpected(identifier); - } - - args = match('(') ? parseArguments() : []; - - return delegate.createFilter(identifier.value, args); - } - - // Filters :: - // "|" Filter - // Filters "|" Filter - - function parseFilters() { - while (match('|')) { - lex(); - parseFilter(); - } - } - - // TopLevel :: - // LabelledExpressions - // AsExpression - // InExpression - // FilterExpression - - // AsExpression :: - // FilterExpression as Identifier - - // InExpression :: - // Identifier, Identifier in FilterExpression - // Identifier in FilterExpression - - // FilterExpression :: - // Expression - // Expression Filters - - function parseTopLevel() { - skipWhitespace(); - peek(); - - var expr = parseExpression(); - if (expr) { - if (lookahead.value === ',' || lookahead.value == 'in' && - expr.type === Syntax.Identifier) { - parseInExpression(expr); - } else { - parseFilters(); - if (lookahead.value === 'as') { - parseAsExpression(expr); - } else { - delegate.createTopLevel(expr); - } - } - } - - if (lookahead.type !== Token.EOF) { - throwUnexpected(lookahead); - } - } - - function parseAsExpression(expr) { - lex(); // as - var identifier = lex().value; - delegate.createAsExpression(expr, identifier); - } - - function parseInExpression(identifier) { - var indexName; - if (lookahead.value === ',') { - lex(); - if (lookahead.type !== Token.Identifier) - throwUnexpected(lookahead); - indexName = lex().value; - } - - lex(); // in - var expr = parseExpression(); - parseFilters(); - delegate.createInExpression(identifier.name, indexName, expr); - } - - function parse(code, inDelegate) { - delegate = inDelegate; - source = code; - index = 0; - length = source.length; - lookahead = null; - state = { - labelSet: {} - }; - - return parseTopLevel(); - } - - global.esprima = { - parse: parse - }; -})(module.exports); \ No newline at end of file diff --git a/packages/core/js-libs/esprima/package.json b/packages/core/js-libs/esprima/package.json deleted file mode 100644 index 3ee3e4bd7d..0000000000 --- a/packages/core/js-libs/esprima/package.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "name": "esprima", - "description": "ECMAScript parsing infrastructure for multipurpose analysis", - "homepage": "http://esprima.org", - "main": "esprima", - "sideEffects": false, - "types": "esprima.d.ts", - "bin": { - "esparse": "./bin/esparse.js", - "esvalidate": "./bin/esvalidate.js" - }, - "version": "2.0.0-dev", - "engines": { - "node": ">=0.4.0" - }, - "author": { - "name": "Ariya Hidayat", - "email": "ariya.hidayat@gmail.com" - }, - "maintainers": [ - { - "name": "Ariya Hidayat", - "email": "ariya.hidayat@gmail.com", - "web": "http://ariya.ofilabs.com" - } - ], - "repository": { - "type": "git", - "url": "https://github.com/ariya/esprima.git" - }, - "bugs": { - "url": "http://issues.esprima.org" - }, - "licenses": [ - { - "type": "BSD", - "url": "https://github.com/ariya/esprima/raw/master/LICENSE.BSD" - } - ], - "devDependencies": { - "jslint": "~0.1.9", - "eslint": "~5.16.0", - "jscs": "~1.2.4", - "istanbul": "~0.2.6", - "complexity-report": "~0.6.1", - "regenerate": "~0.6.2", - "unicode-7.0.0": "~0.1.5", - "json-diff": "~0.3.1", - "optimist": "~0.6.0" - }, - "keywords": [ - "ast", - "ecmascript", - "javascript", - "parser", - "syntax" - ], - "scripts": { - "generate-regex": "node tools/generate-identifier-regex.js", - "test": "npm run-script lint && node test/run.js && npm run-script coverage && npm run-script complexity", - "lint": "npm run-script check-version && npm run-script eslint && npm run-script jscs && npm run-script jslint", - "check-version": "node tools/check-version.js", - "eslint": "node node_modules/eslint/bin/eslint.js esprima.js", - "jscs": "node node_modules/jscs/bin/jscs esprima.js", - "jslint": "node node_modules/jslint/bin/jslint.js esprima.js", - "coverage": "npm run-script analyze-coverage && npm run-script check-coverage", - "analyze-coverage": "node node_modules/istanbul/lib/cli.js cover test/runner.js", - "check-coverage": "node node_modules/istanbul/lib/cli.js check-coverage --statement 100 --branch 100 --function 100", - "complexity": "npm run-script analyze-complexity && npm run-script check-complexity", - "analyze-complexity": "node tools/list-complexity.js", - "check-complexity": "node node_modules/complexity-report/src/cli.js --maxcc 15 --silent -l -w esprima.js", - "benchmark": "node test/benchmarks.js", - "benchmark-quick": "node test/benchmarks.js quick" - } -} diff --git a/packages/core/js-libs/polymer-expressions/LICENSE b/packages/core/js-libs/polymer-expressions/LICENSE deleted file mode 100644 index 95987bac86..0000000000 --- a/packages/core/js-libs/polymer-expressions/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2014 The Polymer Authors. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/core/js-libs/polymer-expressions/README.md b/packages/core/js-libs/polymer-expressions/README.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/core/js-libs/polymer-expressions/package.json b/packages/core/js-libs/polymer-expressions/package.json deleted file mode 100644 index 099137b475..0000000000 --- a/packages/core/js-libs/polymer-expressions/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "sideEffects": false, - "name": "polymer-expressions", - "main": "polymer-expressions", - "types": "polymer-expressions.d.ts" -} diff --git a/packages/core/js-libs/polymer-expressions/path-parser.js b/packages/core/js-libs/polymer-expressions/path-parser.js deleted file mode 100644 index dbcd32ba16..0000000000 --- a/packages/core/js-libs/polymer-expressions/path-parser.js +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt - */ - -'use strict'; - -function detectEval() { - // Don't test for eval if we're running in a Chrome App environment. - // We check for APIs set that only exist in a Chrome App context. - if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) { - return false; - } - - // Firefox OS Apps do not allow eval. This feature detection is very hacky - // but even if some other platform adds support for this function this code - // will continue to work. - if (typeof navigator != 'undefined' && navigator.getDeviceStorage) { - return false; - } - - try { - var f = new Function('', 'return true;'); - return f(); - } catch (ex) { - return false; - } -} - -var hasEval = detectEval(); - -function isIndex(s) { - return +s === s >>> 0 && s !== ''; -} - -function toNumber(s) { - return +s; -} - -function isObject(obj) { - return obj === Object(obj); -} - -var numberIsNaN = Number.isNaN || function (value) { - return typeof value === 'number' && isNaN(value); -} - -function areSameValue(left, right) { - if (left === right) - return left !== 0 || 1 / left === 1 / right; - if (numberIsNaN(left) && numberIsNaN(right)) - return true; - - return left !== left && right !== right; -} - -var createObject = ('__proto__' in {}) ? - function (obj) { return obj; } : - function (obj) { - var proto = obj.__proto__; - if (!proto) - return obj; - var newObject = Object.create(proto); - Object.getOwnPropertyNames(obj).forEach(function (name) { - Object.defineProperty(newObject, name, - Object.getOwnPropertyDescriptor(obj, name)); - }); - return newObject; - }; - -var identStart = '[\$_a-zA-Z]'; -var identPart = '[\$_a-zA-Z0-9]'; -var identRegExp = new RegExp('^' + identStart + '+' + identPart + '*' + '$'); - -function getPathCharType(char) { - if (char === undefined) - return 'eof'; - - var code = char.charCodeAt(0); - - switch (code) { - case 0x5B: // [ - case 0x5D: // ] - case 0x2E: // . - case 0x22: // " - case 0x27: // ' - case 0x30: // 0 - return char; - - case 0x5F: // _ - case 0x24: // $ - return 'ident'; - - case 0x20: // Space - case 0x09: // Tab - case 0x0A: // Newline - case 0x0D: // Return - case 0xA0: // No-break space - case 0xFEFF: // Byte Order Mark - case 0x2028: // Line Separator - case 0x2029: // Paragraph Separator - return 'ws'; - } - - // a-z, A-Z - if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A)) - return 'ident'; - - // 1-9 - if (0x31 <= code && code <= 0x39) - return 'number'; - - return 'else'; -} - -var pathStateMachine = { - 'beforePath': { - 'ws': ['beforePath'], - 'ident': ['inIdent', 'append'], - '[': ['beforeElement'], - 'eof': ['afterPath'] - }, - - 'inPath': { - 'ws': ['inPath'], - '.': ['beforeIdent'], - '[': ['beforeElement'], - 'eof': ['afterPath'] - }, - - 'beforeIdent': { - 'ws': ['beforeIdent'], - 'ident': ['inIdent', 'append'] - }, - - 'inIdent': { - 'ident': ['inIdent', 'append'], - '0': ['inIdent', 'append'], - 'number': ['inIdent', 'append'], - 'ws': ['inPath', 'push'], - '.': ['beforeIdent', 'push'], - '[': ['beforeElement', 'push'], - 'eof': ['afterPath', 'push'] - }, - - 'beforeElement': { - 'ws': ['beforeElement'], - '0': ['afterZero', 'append'], - 'number': ['inIndex', 'append'], - "'": ['inSingleQuote', 'append', ''], - '"': ['inDoubleQuote', 'append', ''] - }, - - 'afterZero': { - 'ws': ['afterElement', 'push'], - ']': ['inPath', 'push'] - }, - - 'inIndex': { - '0': ['inIndex', 'append'], - 'number': ['inIndex', 'append'], - 'ws': ['afterElement'], - ']': ['inPath', 'push'] - }, - - 'inSingleQuote': { - "'": ['afterElement'], - 'eof': ['error'], - 'else': ['inSingleQuote', 'append'] - }, - - 'inDoubleQuote': { - '"': ['afterElement'], - 'eof': ['error'], - 'else': ['inDoubleQuote', 'append'] - }, - - 'afterElement': { - 'ws': ['afterElement'], - ']': ['inPath', 'push'] - } -} - -function noop() { } - -function parsePath(path) { - var keys = []; - var index = -1; - var c, newChar, key, type, transition, action, typeMap, mode = 'beforePath'; - - var actions = { - push: function () { - if (key === undefined) - return; - - keys.push(key); - key = undefined; - }, - - append: function () { - if (key === undefined) - key = newChar - else - key += newChar; - } - }; - - function maybeUnescapeQuote() { - if (index >= path.length) - return; - - var nextChar = path[index + 1]; - if ((mode == 'inSingleQuote' && nextChar == "'") || - (mode == 'inDoubleQuote' && nextChar == '"')) { - index++; - newChar = nextChar; - actions.append(); - return true; - } - } - - while (mode) { - index++; - c = path[index]; - - if (c == '\\' && maybeUnescapeQuote(mode)) - continue; - - type = getPathCharType(c); - typeMap = pathStateMachine[mode]; - transition = typeMap[type] || typeMap['else'] || 'error'; - - if (transition == 'error') - return; // parse error; - - mode = transition[0]; - action = actions[transition[1]] || noop; - newChar = transition[2] === undefined ? c : transition[2]; - action(); - - if (mode === 'afterPath') { - return keys; - } - } - - return; // parse error -} - -function isIdent(s) { - return identRegExp.test(s); -} - -var constructorIsPrivate = {}; - -function Path(parts, privateToken) { - if (privateToken !== constructorIsPrivate) - throw Error('Use Path.get to retrieve path objects'); - - for (var i = 0; i < parts.length; i++) { - this.push(String(parts[i])); - } - - if (hasEval && this.length) { - this.getValueFrom = this.compiledGetValueFromFn(); - } -} - -// TODO(rafaelw): Make simple LRU cache -var pathCache = {}; - -function getPath(pathString) { - if (pathString instanceof Path) - return pathString; - - if (pathString == null || pathString.length == 0) - pathString = ''; - - if (typeof pathString != 'string') { - if (isIndex(pathString.length)) { - // Constructed with array-like (pre-parsed) keys - return new Path(pathString, constructorIsPrivate); - } - - pathString = String(pathString); - } - - var path = pathCache[pathString]; - if (path) - return path; - - var parts = parsePath(pathString); - if (!parts) - return invalidPath; - - var path = new Path(parts, constructorIsPrivate); - pathCache[pathString] = path; - return path; -} - -Path.get = getPath; - -function formatAccessor(key) { - if (isIndex(key)) { - return '[' + key + ']'; - } else { - return '["' + key.replace(/"/g, '\\"') + '"]'; - } -} - -Path.prototype = createObject({ - __proto__: [], - valid: true, - - toString: function () { - var pathString = ''; - for (var i = 0; i < this.length; i++) { - var key = this[i]; - if (isIdent(key)) { - pathString += i ? '.' + key : key; - } else { - pathString += formatAccessor(key); - } - } - - return pathString; - }, - - getValueFrom: function (obj, directObserver) { - for (var i = 0; i < this.length; i++) { - if (obj == null) - return; - obj = obj[this[i]]; - } - return obj; - }, - - iterateObjects: function (obj, observe) { - for (var i = 0; i < this.length; i++) { - if (i) - obj = obj[this[i - 1]]; - if (!isObject(obj)) - return; - observe(obj, this[i]); - } - }, - - compiledGetValueFromFn: function () { - var str = ''; - var pathString = 'obj'; - str += 'if (obj != null'; - var i = 0; - var key; - for (; i < (this.length - 1) ; i++) { - key = this[i]; - pathString += isIdent(key) ? '.' + key : formatAccessor(key); - str += ' &&\n ' + pathString + ' != null'; - } - str += ')\n'; - - var key = this[i]; - pathString += isIdent(key) ? '.' + key : formatAccessor(key); - - str += ' return ' + pathString + ';\nelse\n return undefined;'; - return new Function('obj', str); - }, - - setValueFrom: function (obj, value) { - if (!this.length) - return false; - - for (var i = 0; i < this.length - 1; i++) { - if (!isObject(obj)) - return false; - obj = obj[this[i]]; - } - - if (!isObject(obj)) - return false; - - obj[this[i]] = value; - return true; - } -}); - -var invalidPath = new Path('', constructorIsPrivate); -invalidPath.valid = false; -invalidPath.getValueFrom = invalidPath.setValueFrom = function () { }; - -exports.Path = Path; diff --git a/packages/core/js-libs/polymer-expressions/polymer-expressions.d.ts b/packages/core/js-libs/polymer-expressions/polymer-expressions.d.ts deleted file mode 100644 index 444f06fc7c..0000000000 --- a/packages/core/js-libs/polymer-expressions/polymer-expressions.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -//@private -export class PolymerExpressions { - static getExpression(expression: string): Expression; -} - -export class Expression { - /** - * Evaluates a value for an expression. - * @param model - Context of the expression. - * @param isBackConvert - Denotes if the convertion is forward (from model to ui) or back (ui to model). - * @param changedModel - A property bag which contains all changed properties (in case of two way binding). - */ - getValue(model, isBackConvert, changedModel); -} diff --git a/packages/core/js-libs/polymer-expressions/polymer-expressions.js b/packages/core/js-libs/polymer-expressions/polymer-expressions.js deleted file mode 100644 index 1a1ddea977..0000000000 --- a/packages/core/js-libs/polymer-expressions/polymer-expressions.js +++ /dev/null @@ -1,541 +0,0 @@ -// Copyright (c) 2014 The Polymer Project Authors. All rights reserved. -// This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt -// The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt -// The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt -// Code distributed by Google as part of the polymer project is also -// subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt - -// Hack to resolve https://github.com/webpack/enhanced-resolve/issues/197 . -// This issue causes an require like this (`../esprima`) to be resolved to (`esprima`) by the Angular webpack plugin -var esprima = require("../../js-libs/esprima").esprima; - -var Path = require("./path-parser").Path; - -(function (global) { - 'use strict'; - - // TODO(rafaelw): Implement simple LRU. - var expressionParseCache = Object.create(null); - - function getExpression(expressionText) { - var expression = expressionParseCache[expressionText]; - if (!expression) { - var delegate = new ASTDelegate(); - esprima.parse(expressionText, delegate); - expression = new Expression(delegate); - expressionParseCache[expressionText] = expression; - } - return expression; - } - - function Literal(value) { - this.value = value; - this.valueFn_ = undefined; - } - - Literal.prototype = { - valueFn: function () { - if (!this.valueFn_) { - var value = this.value; - this.valueFn_ = function () { - return value; - } - } - - return this.valueFn_; - } - } - - function IdentPath(name) { - this.name = name; - this.path = Path.get(name); - } - - IdentPath.prototype = { - valueFn: function () { - if (!this.valueFn_) { - var name = this.name; - var path = this.path; - this.valueFn_ = function (model, observer, changedModel) { - if (observer) - observer.addPath(model, path); - - if (changedModel) { - var result = path.getValueFrom(changedModel); - if (result !== undefined) { - return result; - } - } - - return path.getValueFrom(model); - } - } - - return this.valueFn_; - }, - - setValue: function (model, newValue) { - if (this.path.length == 1) { - model = findScope(model, this.path[0]); - } - - return this.path.setValueFrom(model, newValue); - } - }; - - function MemberExpression(object, property, accessor) { - this.computed = accessor == '['; - - this.dynamicDeps = typeof object == 'function' || - object.dynamicDeps || - (this.computed && !(property instanceof Literal)); - - this.simplePath = - !this.dynamicDeps && - (property instanceof IdentPath || property instanceof Literal) && - (object instanceof MemberExpression || object instanceof IdentPath); - - this.object = this.simplePath ? object : getFn(object); - this.property = !this.computed || this.simplePath ? - property : getFn(property); - } - - MemberExpression.prototype = { - get fullPath() { - if (!this.fullPath_) { - - var parts = this.object instanceof MemberExpression ? - this.object.fullPath.slice() : [this.object.name]; - parts.push(this.property instanceof IdentPath ? - this.property.name : this.property.value); - this.fullPath_ = Path.get(parts); - } - - return this.fullPath_; - }, - - valueFn: function () { - if (!this.valueFn_) { - var object = this.object; - - if (this.simplePath) { - var path = this.fullPath; - - this.valueFn_ = function (model, observer) { - if (observer) - observer.addPath(model, path); - - return path.getValueFrom(model); - }; - } else if (!this.computed) { - var path = Path.get(this.property.name); - - this.valueFn_ = function (model, observer, filterRegistry) { - var context = object(model, observer, filterRegistry); - - if (observer) - observer.addPath(context, path); - - return path.getValueFrom(context); - } - } else { - // Computed property. - var property = this.property; - - this.valueFn_ = function (model, observer, filterRegistry) { - var context = object(model, observer, filterRegistry); - var propName = property(model, observer, filterRegistry); - if (observer) - observer.addPath(context, [propName]); - - return context ? context[propName] : undefined; - }; - } - } - return this.valueFn_; - }, - - setValue: function (model, newValue) { - if (this.simplePath) { - this.fullPath.setValueFrom(model, newValue); - return newValue; - } - - var object = this.object(model); - var propName = this.property instanceof IdentPath ? this.property.name : - this.property(model); - return object[propName] = newValue; - } - }; - - function Filter(name, args) { - this.name = name; - this.args = []; - for (var i = 0; i < args.length; i++) { - this.args[i] = getFn(args[i]); - } - } - - Filter.prototype = { - transform: function (model, observer, filterRegistry, toModelDirection, - initialArgs) { - var fn = filterRegistry[this.name]; - var context = model; - if (fn) { - context = undefined; - } else { - fn = context[this.name]; - if (!fn) { - console.error('Cannot find function or filter: ' + this.name); - return; - } - } - - // If toModelDirection is falsey, then the "normal" (dom-bound) direction - // is used. Otherwise, it looks for a 'toModel' property function on the - // object. - if (toModelDirection) { - fn = fn.toModel; - } else if (typeof fn.toView == 'function') { - fn = fn.toView; - } - - if (typeof fn != 'function') { - console.error('Cannot find function or filter: ' + this.name); - return; - } - - var args = initialArgs || []; - for (var i = 0; i < this.args.length; i++) { - args.push(getFn(this.args[i])(model, observer, filterRegistry)); - } - - return fn.apply(context, args); - } - }; - - function notImplemented() { throw Error('Not Implemented'); } - - var unaryOperators = { - '+': function (v) { return +v; }, - '-': function (v) { return -v; }, - '!': function (v) { return !v; } - }; - - var binaryOperators = { - '+': function (l, r) { return l + r; }, - '-': function (l, r) { return l - r; }, - '*': function (l, r) { return l * r; }, - '/': function (l, r) { return l / r; }, - '%': function (l, r) { return l % r; }, - '<': function (l, r) { return l < r; }, - '>': function (l, r) { return l > r; }, - '<=': function (l, r) { return l <= r; }, - '>=': function (l, r) { return l >= r; }, - '==': function (l, r) { return l == r; }, - '!=': function (l, r) { return l != r; }, - '===': function (l, r) { return l === r; }, - '!==': function (l, r) { return l !== r; }, - '&&': function (l, r) { return l && r; }, - '||': function (l, r) { return l || r; }, - }; - - function getFn(arg) { - return typeof arg == 'function' ? arg : arg.valueFn(); - } - - function ASTDelegate() { - this.expression = null; - this.filters = []; - this.deps = {}; - this.currentPath = undefined; - this.scopeIdent = undefined; - this.indexIdent = undefined; - this.dynamicDeps = false; - } - - ASTDelegate.prototype = { - createUnaryExpression: function (op, argument) { - if (!unaryOperators[op]) - throw Error('Disallowed operator: ' + op); - - argument = getFn(argument); - - return function (model, observer, filterRegistry) { - return unaryOperators[op](argument(model, observer, filterRegistry)); - }; - }, - - createBinaryExpression: function (op, left, right) { - if (!binaryOperators[op]) - throw Error('Disallowed operator: ' + op); - - left = getFn(left); - right = getFn(right); - - switch (op) { - case '||': - this.dynamicDeps = true; - return function (model, observer, filterRegistry) { - return left(model, observer, filterRegistry) || - right(model, observer, filterRegistry); - }; - case '&&': - this.dynamicDeps = true; - return function (model, observer, filterRegistry) { - return left(model, observer, filterRegistry) && - right(model, observer, filterRegistry); - }; - } - - return function (model, observer, filterRegistry) { - return binaryOperators[op](left(model, observer, filterRegistry), - right(model, observer, filterRegistry)); - }; - }, - - createConditionalExpression: function (test, consequent, alternate) { - test = getFn(test); - consequent = getFn(consequent); - alternate = getFn(alternate); - - this.dynamicDeps = true; - - return function (model, observer, filterRegistry) { - return test(model, observer, filterRegistry) ? - consequent(model, observer, filterRegistry) : - alternate(model, observer, filterRegistry); - } - }, - - createIdentifier: function (name) { - var ident = new IdentPath(name); - ident.type = 'Identifier'; - return ident; - }, - - createMemberExpression: function (accessor, object, property) { - var ex = new MemberExpression(object, property, accessor); - if (ex.dynamicDeps) - this.dynamicDeps = true; - return ex; - }, - - createCallExpression: function (expression, args) { - if (!(expression instanceof IdentPath)) - throw Error('Only identifier function invocations are allowed'); - - var filter = new Filter(expression.name, args); - - return function (model, observer, filterRegistry) { - return filter.transform(model, observer, filterRegistry, false); - }; - }, - - createLiteral: function (token) { - return new Literal(token.value); - }, - - createArrayExpression: function (elements) { - for (var i = 0; i < elements.length; i++) - elements[i] = getFn(elements[i]); - - return function (model, observer, filterRegistry) { - var arr = [] - for (var i = 0; i < elements.length; i++) - arr.push(elements[i](model, observer, filterRegistry)); - return arr; - } - }, - - createProperty: function (kind, key, value) { - return { - key: key instanceof IdentPath ? key.name : key.value, - value: value - }; - }, - - createObjectExpression: function (properties) { - for (var i = 0; i < properties.length; i++) - properties[i].value = getFn(properties[i].value); - - return function (model, observer, filterRegistry) { - var obj = {}; - for (var i = 0; i < properties.length; i++) - obj[properties[i].key] = - properties[i].value(model, observer, filterRegistry); - return obj; - } - }, - - createFilter: function (name, args) { - this.filters.push(new Filter(name, args)); - }, - - createAsExpression: function (expression, scopeIdent) { - this.expression = expression; - this.scopeIdent = scopeIdent; - }, - - createInExpression: function (scopeIdent, indexIdent, expression) { - this.expression = expression; - this.scopeIdent = scopeIdent; - this.indexIdent = indexIdent; - }, - - createTopLevel: function (expression) { - this.expression = expression; - }, - - createThisExpression: notImplemented - } - - function Expression(delegate) { - this.scopeIdent = delegate.scopeIdent; - this.indexIdent = delegate.indexIdent; - - if (!delegate.expression) - throw Error('No expression found.'); - - this.expression = delegate.expression; - getFn(this.expression); // forces enumeration of path dependencies - - this.filters = delegate.filters; - this.dynamicDeps = delegate.dynamicDeps; - } - - Expression.prototype = { - getValue: function (model, isBackConvert, changedModel, observer) { - var value = getFn(this.expression)(model.context, observer, changedModel); - for (var i = 0; i < this.filters.length; i++) { - value = this.filters[i].transform(model.context, observer, model.context, isBackConvert, [value]); - } - - return value; - }, - - setValue: function (model, newValue, filterRegistry) { - var count = this.filters ? this.filters.length : 0; - while (count-- > 0) { - newValue = this.filters[count].transform(model, undefined, - filterRegistry, true, [newValue]); - } - - if (this.expression.setValue) - return this.expression.setValue(model, newValue); - } - } - - /** - * Converts a style property name to a css property name. For example: - * "WebkitUserSelect" to "-webkit-user-select" - */ - function convertStylePropertyName(name) { - return String(name).replace(/[A-Z]/g, function (c) { - return '-' + c.toLowerCase(); - }); - } - - var parentScopeName = '@' + Math.random().toString(36).slice(2); - - // Single ident paths must bind directly to the appropriate scope object. - // I.e. Pushed values in two-bindings need to be assigned to the actual model - // object. - function findScope(model, prop) { - while (model[parentScopeName] && - !Object.prototype.hasOwnProperty.call(model, prop)) { - model = model[parentScopeName]; - } - - return model; - } - - function isLiteralExpression(pathString) { - switch (pathString) { - case '': - return false; - - case 'false': - case 'null': - case 'true': - return true; - } - - if (!isNaN(Number(pathString))) - return true; - - return false; - }; - - function PolymerExpressions() { } - - PolymerExpressions.prototype = { - // "built-in" filters - styleObject: function (value) { - var parts = []; - for (var key in value) { - parts.push(convertStylePropertyName(key) + ': ' + value[key]); - } - return parts.join('; '); - }, - - tokenList: function (value) { - var tokens = []; - for (var key in value) { - if (value[key]) - tokens.push(key); - } - return tokens.join(' '); - }, - - // binding delegate API - prepareInstancePositionChanged: function (template) { - var indexIdent = template.polymerExpressionIndexIdent_; - if (!indexIdent) - return; - - return function (templateInstance, index) { - templateInstance.model[indexIdent] = index; - }; - }, - - prepareInstanceModel: function (template) { - var scopeName = template.polymerExpressionScopeIdent_; - if (!scopeName) - return; - - var parentScope = template.templateInstance ? - template.templateInstance.model : - template.model; - - var indexName = template.polymerExpressionIndexIdent_; - - return function (model) { - return createScopeObject(parentScope, model, scopeName, indexName); - }; - } - }; - - var createScopeObject = ('__proto__' in {}) ? - function (parentScope, model, scopeName, indexName) { - var scope = {}; - scope[scopeName] = model; - scope[indexName] = undefined; - scope[parentScopeName] = parentScope; - scope.__proto__ = parentScope; - return scope; - } : - function (parentScope, model, scopeName, indexName) { - var scope = Object.create(parentScope); - Object.defineProperty(scope, scopeName, - { value: model, configurable: true, writable: true }); - Object.defineProperty(scope, indexName, - { value: undefined, configurable: true, writable: true }); - Object.defineProperty(scope, parentScopeName, - { value: parentScope, configurable: true, writable: true }); - return scope; - }; - - global.PolymerExpressions = PolymerExpressions; - PolymerExpressions.getExpression = getExpression; -})(module.exports); \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json index dede394d60..eba3dd0886 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -6,8 +6,8 @@ "version": "8.1.5", "homepage": "https://nativescript.org", "repository": { - "type": "git", - "url": "https://github.com/NativeScript/NativeScript" + "type": "git", + "url": "https://github.com/NativeScript/NativeScript" }, "sideEffects": [ "bundle-entry-points.js", @@ -15,36 +15,37 @@ "./globals" ], "files": [ - "**/*.d.ts", - "**/*.js", - "**/*.map", - "**/platforms/ios/**", - "**/platforms/android/**", - "**/package.json" + "**/*.d.ts", + "**/*.js", + "**/*.map", + "**/platforms/ios/**", + "**/platforms/android/**", + "**/package.json" ], "license": "Apache-2.0", "scripts": { - "postinstall": "node cli-hooks/postinstall.js", - "preuninstall": "node cli-hooks/preuninstall.js" + "postinstall": "node cli-hooks/postinstall.js", + "preuninstall": "node cli-hooks/preuninstall.js" }, "dependencies": { - "css-tree": "^1.1.2", "@nativescript/hook": "~2.0.0", + "css-tree": "^1.1.2", + "esprima": "^4.0.1", "reduce-css-calc": "^2.1.7", "tslib": "~2.0.0" }, "nativescript": { - "platforms": { - "ios": "6.0.0", - "android": "6.0.0" - }, - "hooks": [ - { - "name": "nativescript-core", - "type": "before-checkForChanges", - "script": "cli-hooks/before-checkForChanges.js", - "inject": true - } - ] + "platforms": { + "ios": "6.0.0", + "android": "6.0.0" + }, + "hooks": [ + { + "name": "nativescript-core", + "type": "before-checkForChanges", + "script": "cli-hooks/before-checkForChanges.js", + "inject": true + } + ] } } diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts new file mode 100644 index 0000000000..c0671a1863 --- /dev/null +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -0,0 +1,167 @@ +import { parse } from 'esprima'; +import { Trace } from '../../../trace'; + +const expressionsCache = {}; + +const unaryOperators = { + '+': (v) => +v, + '-': (v) => -v, + '!': (v) => !v, +}; + +const leftRightOperators = { + '+': (l, r) => l + r, + '-': (l, r) => l - r, + '*': (l, r) => l * r, + '/': (l, r) => l / r, + '%': (l, r) => l % r, + '<': (l, r) => l < r, + '>': (l, r) => l > r, + '<=': (l, r) => l <= r, + '>=': (l, r) => l >= r, + '==': (l, r) => l == r, + '!=': (l, r) => l != r, + '===': (l, r) => l === r, + '!==': (l, r) => l !== r, + '&&': (l, r) => l && r, + '|': (l, r) => l | r, + '||': (l, r) => l || r, + in: (l, r) => l in r, + instanceof: (l, r) => l instanceof r, +}; + +const expressionParsers = { + ArrayExpression: (expression, model, isBackConvert, changedModel) => { + const parsed = []; + for (let element of expression.elements) { + let value = convertExpressionToValue(element, model, isBackConvert, changedModel); + element.type === 'SpreadElement' ? parsed.push(...value) : parsed.push(value); + } + return parsed; + }, + BinaryExpression: (expression, model, isBackConvert, changedModel) => { + if (!leftRightOperators[expression.operator]) { + throw Error('Disallowed operator: ' + expression.operator); + } + + const left = convertExpressionToValue(expression.left, model, isBackConvert, changedModel); + const right = convertExpressionToValue(expression.right, model, isBackConvert, changedModel); + + if (expression.operator == '|') { + if (right.formatter && right.arguments) { + right.arguments.unshift(left); + return right.formatter.apply(changedModel, right.arguments); + } + throw Error('Failed to find a valid converter after ' + expression.operator + ' operator'); + } + + return leftRightOperators[expression.operator](left, right); + }, + CallExpression: (expression, model, isBackConvert, changedModel) => { + const callback = convertExpressionToValue(expression.callee, model, isBackConvert, changedModel); + let context = changedModel[expression.callee.name] ? changedModel : model; + + const parsedArgs = []; + for (let argument of expression.arguments) { + let value = convertExpressionToValue(argument, model, isBackConvert, changedModel); + argument.type === 'SpreadElement' ? parsedArgs.push(...value) : parsedArgs.push(value); + } + + const converter = getConverterCallback(callback, parsedArgs, isBackConvert); + return converter ? converter : callback.apply(model, parsedArgs); + }, + ConditionalExpression: (expression, model, isBackConvert, changedModel) => { + const test = convertExpressionToValue(expression.test, model, isBackConvert, changedModel); + const consequent = convertExpressionToValue(expression.consequent, model, isBackConvert, changedModel); + const alternate = convertExpressionToValue(expression.alternate, model, isBackConvert, changedModel); + return test ? consequent : alternate; + }, + Identifier: (expression, model, isBackConvert, changedModel) => { + let context = changedModel[expression.name] ? changedModel : model; + return context[expression.name]; + }, + Literal: (expression, model, isBackConvert, changedModel) => { + return expression.value; + }, + LogicalExpression: (expression, model, isBackConvert, changedModel) => { + if (!leftRightOperators[expression.operator]) { + throw Error('Disallowed operator: ' + expression.operator); + } + + const left = convertExpressionToValue(expression.left, model, isBackConvert, changedModel); + const right = convertExpressionToValue(expression.right, model, isBackConvert, changedModel); + + return leftRightOperators[expression.operator](left, right); + }, + MemberExpression: (expression, model, isBackConvert, changedModel) => { + let object = convertExpressionToValue(expression.object, model, isBackConvert, changedModel); + let property = convertExpressionToValue(expression.property, { context: object }, isBackConvert, object); + return property; + }, + NewExpression: (expression, model, isBackConvert, changedModel) => { + const callback = convertExpressionToValue(expression.callee, model, isBackConvert, changedModel); + let context = changedModel[expression.callee.name] ? changedModel : model; + + const parsedArgs = []; + for (let argument of expression.arguments) { + let value = convertExpressionToValue(argument, model, isBackConvert, changedModel); + argument.type === 'SpreadElement' ? parsedArgs.push(...value) : parsedArgs.push(value); + } + return new callback(...parsedArgs); + }, + ObjectExpression: (expression, model, isBackConvert, changedModel) => { + const parsed = {}; + for (let property of expression.properties) { + const key = convertExpressionToValue(expression.key, model, isBackConvert, changedModel); + const value = convertExpressionToValue(expression.value, model, isBackConvert, changedModel); + parsed[key] = value; + } + return parsed; + }, + SpreadElement: (expression, model, isBackConvert, changedModel) => { + const argument = convertExpressionToValue(expression.argument, model, isBackConvert, changedModel); + return argument; + }, + UnaryExpression: (expression, model, isBackConvert, changedModel) => { + if (!unaryOperators[expression.operator]) { + throw Error('Disallowed operator: ' + expression.operator); + } + + const argument = convertExpressionToValue(expression.argument, model, isBackConvert, changedModel); + return unaryOperators[expression.operator](argument); + }, +}; + +function getConverterCallback(callback, args, isBackConvert) { + let converter = null; + if (typeof callback !== 'function') { + if (typeof callback.toModel === 'function' || typeof callback.toView === 'function') { + if (isBackConvert) { + converter = { formatter: callback.toModel || Function.prototype, arguments: args }; + } else { + converter = { formatter: callback.toView || Function.prototype, arguments: args }; + } + } + } + return converter; +} + +export function parseExpression(expressionText) { + let expression = expressionsCache[expressionText]; + if (!expression) { + let syntax = parse(expressionText); + let statements = syntax.body; + for (let statement of statements) { + if (statement.type == 'ExpressionStatement') { + expression = statement.expression; + break; + } + } + expressionsCache[expressionText] = expression; + } + return expression; +} + +export function convertExpressionToValue(expression, model, isBackConvert, changedModel) { + return expressionParsers[expression.type](expression, model, isBackConvert, changedModel); +} diff --git a/packages/core/ui/core/bindable/index.ts b/packages/core/ui/core/bindable/index.ts index d0dad11f60..006189e953 100644 --- a/packages/core/ui/core/bindable/index.ts +++ b/packages/core/ui/core/bindable/index.ts @@ -8,10 +8,9 @@ import { addWeakEventListener, removeWeakEventListener } from '../weak-event-lis import { bindingConstants, parentsRegex } from '../../builder/binding-builder'; import { escapeRegexSymbols } from '../../../utils'; import { Trace } from '../../../trace'; +import { parseExpression, convertExpressionToValue } from './bindable-expressions'; import * as types from '../../../utils/types'; import * as bindableResources from './bindable-resources'; -const polymerExpressions = require('../../../js-libs/polymer-expressions'); -import { PolymerExpressions } from '../../../js-libs/polymer-expressions'; const contextKey = 'context'; // this regex is used to get parameters inside [] for example: @@ -376,10 +375,15 @@ export class Binding { private _getExpressionValue(expression: string, isBackConvert: boolean, changedModel: any): any { if (!__UI_USE_EXTERNAL_RENDERER__) { try { - const exp = PolymerExpressions.getExpression(expression); + let exp; + try { + exp = parseExpression(expression); + } catch (e) { + return e; + } + if (exp) { const context = (this.source && this.source.get && this.source.get()) || global; - const model = {}; const addedProps = []; const resources = bindableResources.get(); for (const prop in resources) { @@ -390,8 +394,7 @@ export class Binding { } this.prepareContextForExpression(context, expression, addedProps); - model[contextKey] = context; - const result = exp.getValue(model, isBackConvert, changedModel ? changedModel : model); + const result = convertExpressionToValue(exp, context, isBackConvert, changedModel ? changedModel : context); // clear added props const addedPropsLength = addedProps.length; for (let i = 0; i < addedPropsLength; i++) { From b75be12a1ed94f1887ff537288a2e5789689ed74 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 31 Dec 2021 02:46:41 +0000 Subject: [PATCH 02/33] chore: Removed unused import. --- packages/core/ui/core/bindable/bindable-expressions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index c0671a1863..3ea857f4f6 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -1,5 +1,4 @@ import { parse } from 'esprima'; -import { Trace } from '../../../trace'; const expressionsCache = {}; From b756d3484eff8dd82c0676bfbe72fdefbdb7f803 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 31 Dec 2021 02:58:45 +0000 Subject: [PATCH 03/33] fix: Wrong model parameter in MemberExpression parser. --- packages/core/ui/core/bindable/bindable-expressions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index 3ea857f4f6..593c41a311 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -94,7 +94,7 @@ const expressionParsers = { }, MemberExpression: (expression, model, isBackConvert, changedModel) => { let object = convertExpressionToValue(expression.object, model, isBackConvert, changedModel); - let property = convertExpressionToValue(expression.property, { context: object }, isBackConvert, object); + let property = convertExpressionToValue(expression.property, object, isBackConvert, object); return property; }, NewExpression: (expression, model, isBackConvert, changedModel) => { From 3bfc6bd22bd84200f537e0aa9aec915d9fc43d3c Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 31 Dec 2021 03:03:50 +0000 Subject: [PATCH 04/33] chore: Minor cleaning for `Identifier` parser. --- packages/core/ui/core/bindable/bindable-expressions.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index 593c41a311..11b56af69d 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -76,8 +76,7 @@ const expressionParsers = { return test ? consequent : alternate; }, Identifier: (expression, model, isBackConvert, changedModel) => { - let context = changedModel[expression.name] ? changedModel : model; - return context[expression.name]; + return changedModel[expression.name] ? changedModel[expression.name] : model[expression.name]; }, Literal: (expression, model, isBackConvert, changedModel) => { return expression.value; From 7affa1ea38c7a6bb461cc692aab6a11abef67f2d Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 31 Dec 2021 11:30:09 +0000 Subject: [PATCH 05/33] feat: Added support for template literals (non-tagged). --- .../core/ui/core/bindable/bindable-expressions.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index 11b56af69d..ac2eb56641 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -120,6 +120,20 @@ const expressionParsers = { const argument = convertExpressionToValue(expression.argument, model, isBackConvert, changedModel); return argument; }, + TemplateElement: (expression, model, isBackConvert, changedModel) => { + return expression.value.cooked; + }, + TemplateLiteral: (expression, model, isBackConvert, changedModel) => { + let parsedText = ''; + for (let q of expression.quasis) { + parsedText += convertExpressionToValue(q, model, isBackConvert, changedModel); + } + + for (let ex of expression.expressions) { + parsedText += convertExpressionToValue(ex, model, isBackConvert, changedModel); + } + return parsedText; + }, UnaryExpression: (expression, model, isBackConvert, changedModel) => { if (!unaryOperators[expression.operator]) { throw Error('Disallowed operator: ' + expression.operator); From 5e3145ccd45c766feb596cee8990b49eba24b72c Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 31 Dec 2021 18:35:09 +0000 Subject: [PATCH 06/33] feat: Added proper support for function context and computed properties. --- .../ui/core/bindable/bindable-expressions.ts | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index ac2eb56641..9589531644 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -1,4 +1,5 @@ import { parse } from 'esprima'; +import { isFunction, isNullOrUndefined, isObject } from '../../../utils/types'; const expressionsCache = {}; @@ -40,25 +41,24 @@ const expressionParsers = { }, BinaryExpression: (expression, model, isBackConvert, changedModel) => { if (!leftRightOperators[expression.operator]) { - throw Error('Disallowed operator: ' + expression.operator); + throw new Error('Disallowed operator: ' + expression.operator); } const left = convertExpressionToValue(expression.left, model, isBackConvert, changedModel); const right = convertExpressionToValue(expression.right, model, isBackConvert, changedModel); if (expression.operator == '|') { - if (right.formatter && right.arguments) { - right.arguments.unshift(left); - return right.formatter.apply(changedModel, right.arguments); + if (right.converterArgs) { + return right(left, ...right.converterArgs); } - throw Error('Failed to find a valid converter after ' + expression.operator + ' operator'); + throw new Error('Invalid converter after ' + expression.operator + ' operator'); } return leftRightOperators[expression.operator](left, right); }, CallExpression: (expression, model, isBackConvert, changedModel) => { const callback = convertExpressionToValue(expression.callee, model, isBackConvert, changedModel); - let context = changedModel[expression.callee.name] ? changedModel : model; + const isConverter = isObject(callback) && (isFunction(callback.toModel) || isFunction(callback.toView)); const parsedArgs = []; for (let argument of expression.arguments) { @@ -66,8 +66,11 @@ const expressionParsers = { argument.type === 'SpreadElement' ? parsedArgs.push(...value) : parsedArgs.push(value); } - const converter = getConverterCallback(callback, parsedArgs, isBackConvert); - return converter ? converter : callback.apply(model, parsedArgs); + if (isNullOrUndefined(callback) || (!isFunction(callback) && !isConverter)) { + throw new Error('Cannot perform a call using a non-function property'); + } + + return isConverter ? getConverterCallback(callback, parsedArgs, isBackConvert) : callback(...parsedArgs); }, ConditionalExpression: (expression, model, isBackConvert, changedModel) => { const test = convertExpressionToValue(expression.test, model, isBackConvert, changedModel); @@ -76,7 +79,8 @@ const expressionParsers = { return test ? consequent : alternate; }, Identifier: (expression, model, isBackConvert, changedModel) => { - return changedModel[expression.name] ? changedModel[expression.name] : model[expression.name]; + const context = changedModel[expression.name] ? changedModel : model; + return getValueWithContext(expression.name, context); }, Literal: (expression, model, isBackConvert, changedModel) => { return expression.value; @@ -92,14 +96,12 @@ const expressionParsers = { return leftRightOperators[expression.operator](left, right); }, MemberExpression: (expression, model, isBackConvert, changedModel) => { - let object = convertExpressionToValue(expression.object, model, isBackConvert, changedModel); - let property = convertExpressionToValue(expression.property, object, isBackConvert, object); - return property; + const object = convertExpressionToValue(expression.object, model, isBackConvert, changedModel); + const property = convertExpressionToValue(expression.property, object, isBackConvert, object); + return expression.computed ? getValueWithContext(property, object) : property; }, NewExpression: (expression, model, isBackConvert, changedModel) => { const callback = convertExpressionToValue(expression.callee, model, isBackConvert, changedModel); - let context = changedModel[expression.callee.name] ? changedModel : model; - const parsedArgs = []; for (let argument of expression.arguments) { let value = convertExpressionToValue(argument, model, isBackConvert, changedModel); @@ -144,18 +146,22 @@ const expressionParsers = { }, }; -function getConverterCallback(callback, args, isBackConvert) { - let converter = null; - if (typeof callback !== 'function') { - if (typeof callback.toModel === 'function' || typeof callback.toView === 'function') { - if (isBackConvert) { - converter = { formatter: callback.toModel || Function.prototype, arguments: args }; - } else { - converter = { formatter: callback.toView || Function.prototype, arguments: args }; - } - } +function getConverterCallback(context, args, isBackConvert) { + let callback = isBackConvert ? context.toModel : context.toView; + if (!callback) { + callback = Function.prototype; + } + callback = callback.bind(context); + callback.converterArgs = args; + return callback; +} + +function getValueWithContext(key, context, isBackConvert) { + let value = context[key]; + if (isFunction(value)) { + value = value.bind(context); } - return converter; + return value; } export function parseExpression(expressionText) { From 0399285f8c476ec6ee029504f237308e5a55907a Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 31 Dec 2021 19:16:46 +0000 Subject: [PATCH 07/33] fix: Do not parse parent expressions until parent view is ready. --- .../ui/core/bindable/bindable-expressions.ts | 2 +- packages/core/ui/core/bindable/index.ts | 32 +++++++------------ 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index 9589531644..37d34c45e1 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -156,7 +156,7 @@ function getConverterCallback(context, args, isBackConvert) { return callback; } -function getValueWithContext(key, context, isBackConvert) { +function getValueWithContext(key, context) { let value = context[key]; if (isFunction(value)) { value = value.bind(context); diff --git a/packages/core/ui/core/bindable/index.ts b/packages/core/ui/core/bindable/index.ts index 006189e953..e29467a2d8 100644 --- a/packages/core/ui/core/bindable/index.ts +++ b/packages/core/ui/core/bindable/index.ts @@ -359,7 +359,7 @@ export class Binding { } const updateExpression = this.prepareExpressionForUpdate(); - this.prepareContextForExpression(changedModel, updateExpression, undefined); + this.prepareContextForExpression(changedModel, updateExpression); const expressionValue = this._getExpressionValue(updateExpression, true, changedModel); if (expressionValue instanceof Error) { @@ -384,25 +384,18 @@ export class Binding { if (exp) { const context = (this.source && this.source.get && this.source.get()) || global; - const addedProps = []; + const model = { ...context }; const resources = bindableResources.get(); for (const prop in resources) { - if (resources.hasOwnProperty(prop) && !context.hasOwnProperty(prop)) { - context[prop] = resources[prop]; - addedProps.push(prop); + if (resources.hasOwnProperty(prop) && !model.hasOwnProperty(prop)) { + model[prop] = resources[prop]; } } - this.prepareContextForExpression(context, expression, addedProps); - const result = convertExpressionToValue(exp, context, isBackConvert, changedModel ? changedModel : context); - // clear added props - const addedPropsLength = addedProps.length; - for (let i = 0; i < addedPropsLength; i++) { - delete context[addedProps[i]]; + if (!this.prepareContextForExpression(model, expression)) { + return ''; } - addedProps.length = 0; - - return result; + return convertExpressionToValue(exp, model, isBackConvert, changedModel ? changedModel : model); } return new Error(expression + ' is not a valid expression.'); @@ -475,18 +468,16 @@ export class Binding { } } - private prepareContextForExpression(model: Object, expression: string, newProps: Array) { + private prepareContextForExpression(model: Object, expression: string) { + let success = true; let parentViewAndIndex: { view: ViewBase; index: number }; let parentView; - const addedProps = newProps || []; + let expressionCP = expression; if (expressionCP.indexOf(bc.bindingValueKey) > -1) { model[bc.bindingValueKey] = model; - addedProps.push(bc.bindingValueKey); } - let success = true; - const parentsArray = expressionCP.match(parentsRegex); if (parentsArray) { for (let i = 0; i < parentsArray.length; i++) { @@ -496,7 +487,6 @@ export class Binding { if (parentViewAndIndex.view) { model[bc.parentsValueKey] = model[bc.parentsValueKey] || {}; model[bc.parentsValueKey][parentViewAndIndex.index] = parentViewAndIndex.view.bindingContext; - addedProps.push(bc.parentsValueKey); } else { success = false; } @@ -507,7 +497,6 @@ export class Binding { parentView = this.getParentView(this.target.get(), bc.parentValueKey).view; if (parentView) { model[bc.parentValueKey] = parentView.bindingContext; - addedProps.push(bc.parentValueKey); } else { success = false; } @@ -519,6 +508,7 @@ export class Binding { targetInstance.off('loaded', this.loadedHandlerVisualTreeBinding, this); targetInstance.on('loaded', this.loadedHandlerVisualTreeBinding, this); } + return success; } private getSourcePropertyValue() { From 2295aadd611d0ae9eec7ab2024700ff749eb74e6 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 31 Dec 2021 19:49:18 +0000 Subject: [PATCH 08/33] chore: Added two-way binding check to ensure expression is not parsed if context is not ready. --- packages/core/ui/core/bindable/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/ui/core/bindable/index.ts b/packages/core/ui/core/bindable/index.ts index e29467a2d8..bc384f4f67 100644 --- a/packages/core/ui/core/bindable/index.ts +++ b/packages/core/ui/core/bindable/index.ts @@ -359,9 +359,9 @@ export class Binding { } const updateExpression = this.prepareExpressionForUpdate(); - this.prepareContextForExpression(changedModel, updateExpression); + const isContextPrepared = this.prepareContextForExpression(changedModel, updateExpression); - const expressionValue = this._getExpressionValue(updateExpression, true, changedModel); + const expressionValue = isContextPrepared ? this._getExpressionValue(updateExpression, true, changedModel) : ''; if (expressionValue instanceof Error) { Trace.write((expressionValue).message, Trace.categories.Binding, Trace.messageType.error); } From 91ac423c920590ea2182e3a50af283bf4a1c965d Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 31 Dec 2021 20:02:28 +0000 Subject: [PATCH 09/33] chore: Small refactor regarding expression value. --- packages/core/ui/core/bindable/index.ts | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/core/ui/core/bindable/index.ts b/packages/core/ui/core/bindable/index.ts index bc384f4f67..86d7070cf0 100644 --- a/packages/core/ui/core/bindable/index.ts +++ b/packages/core/ui/core/bindable/index.ts @@ -359,9 +359,9 @@ export class Binding { } const updateExpression = this.prepareExpressionForUpdate(); - const isContextPrepared = this.prepareContextForExpression(changedModel, updateExpression); + this.prepareContextForExpression(changedModel, updateExpression); - const expressionValue = isContextPrepared ? this._getExpressionValue(updateExpression, true, changedModel) : ''; + const expressionValue = this._getExpressionValue(updateExpression, true, changedModel); if (expressionValue instanceof Error) { Trace.write((expressionValue).message, Trace.categories.Binding, Trace.messageType.error); } @@ -392,7 +392,11 @@ export class Binding { } } + // For expressions, there are also cases when binding must be updated after component is loaded (e.g. ListView) if (!this.prepareContextForExpression(model, expression)) { + const targetInstance = this.target.get(); + targetInstance.off('loaded', this.loadedHandlerVisualTreeBinding, this); + targetInstance.on('loaded', this.loadedHandlerVisualTreeBinding, this); return ''; } return convertExpressionToValue(exp, model, isBackConvert, changedModel ? changedModel : model); @@ -469,6 +473,8 @@ export class Binding { } private prepareContextForExpression(model: Object, expression: string) { + const targetInstance = this.target.get(); + let success = true; let parentViewAndIndex: { view: ViewBase; index: number }; let parentView; @@ -483,7 +489,7 @@ export class Binding { for (let i = 0; i < parentsArray.length; i++) { // This prevents later checks to mistake $parents[] for $parent expressionCP = expressionCP.replace(parentsArray[i], ''); - parentViewAndIndex = this.getParentView(this.target.get(), parentsArray[i]); + parentViewAndIndex = this.getParentView(targetInstance, parentsArray[i]); if (parentViewAndIndex.view) { model[bc.parentsValueKey] = model[bc.parentsValueKey] || {}; model[bc.parentsValueKey][parentViewAndIndex.index] = parentViewAndIndex.view.bindingContext; @@ -494,20 +500,13 @@ export class Binding { } if (expressionCP.indexOf(bc.parentValueKey) > -1) { - parentView = this.getParentView(this.target.get(), bc.parentValueKey).view; + parentView = this.getParentView(targetInstance, bc.parentValueKey).view; if (parentView) { model[bc.parentValueKey] = parentView.bindingContext; } else { success = false; } } - - // For expressions, there are also cases when binding must be updated after component is loaded (e.g. ListView) - if (!success) { - const targetInstance = this.target.get(); - targetInstance.off('loaded', this.loadedHandlerVisualTreeBinding, this); - targetInstance.on('loaded', this.loadedHandlerVisualTreeBinding, this); - } return success; } From 842f7018af251a225c9f741f7527523d0ee17f8f Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Sat, 1 Jan 2022 23:39:52 +0000 Subject: [PATCH 10/33] chore: Added a `prettier` ignore to keep `in` and `instanceof` properties inside quotes. --- packages/core/ui/core/bindable/bindable-expressions.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index 37d34c45e1..077ccae0e1 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -9,6 +9,7 @@ const unaryOperators = { '!': (v) => !v, }; +// prettier-ignore const leftRightOperators = { '+': (l, r) => l + r, '-': (l, r) => l - r, @@ -26,8 +27,8 @@ const leftRightOperators = { '&&': (l, r) => l && r, '|': (l, r) => l | r, '||': (l, r) => l || r, - in: (l, r) => l in r, - instanceof: (l, r) => l instanceof r, + 'in': (l, r) => l in r, + 'instanceof': (l, r) => l instanceof r, }; const expressionParsers = { From 0bfa368e31fb507ff50da685a71a3741d0b29c20 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Sat, 1 Jan 2022 23:43:20 +0000 Subject: [PATCH 11/33] chore: Added null coalescing operator inside schema of supported operators even though esprima 4 does not support it. --- packages/core/ui/core/bindable/bindable-expressions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index 077ccae0e1..a634e81505 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -27,6 +27,7 @@ const leftRightOperators = { '&&': (l, r) => l && r, '|': (l, r) => l | r, '||': (l, r) => l || r, + '??': (l, r) => l ?? r, 'in': (l, r) => l in r, 'instanceof': (l, r) => l instanceof r, }; From 4008d27c7dc98990be0214e409b5cdb444b7cdba Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Sun, 2 Jan 2022 00:27:20 +0000 Subject: [PATCH 12/33] chore: Added prettier-ignore for expression parsers. --- .../ui/core/bindable/bindable-expressions.ts | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index a634e81505..c9c3da9c00 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -32,8 +32,9 @@ const leftRightOperators = { 'instanceof': (l, r) => l instanceof r, }; +// prettier-ignore const expressionParsers = { - ArrayExpression: (expression, model, isBackConvert, changedModel) => { + 'ArrayExpression': (expression, model, isBackConvert, changedModel) => { const parsed = []; for (let element of expression.elements) { let value = convertExpressionToValue(element, model, isBackConvert, changedModel); @@ -41,7 +42,7 @@ const expressionParsers = { } return parsed; }, - BinaryExpression: (expression, model, isBackConvert, changedModel) => { + 'BinaryExpression': (expression, model, isBackConvert, changedModel) => { if (!leftRightOperators[expression.operator]) { throw new Error('Disallowed operator: ' + expression.operator); } @@ -58,7 +59,7 @@ const expressionParsers = { return leftRightOperators[expression.operator](left, right); }, - CallExpression: (expression, model, isBackConvert, changedModel) => { + 'CallExpression': (expression, model, isBackConvert, changedModel) => { const callback = convertExpressionToValue(expression.callee, model, isBackConvert, changedModel); const isConverter = isObject(callback) && (isFunction(callback.toModel) || isFunction(callback.toView)); @@ -74,20 +75,20 @@ const expressionParsers = { return isConverter ? getConverterCallback(callback, parsedArgs, isBackConvert) : callback(...parsedArgs); }, - ConditionalExpression: (expression, model, isBackConvert, changedModel) => { + 'ConditionalExpression': (expression, model, isBackConvert, changedModel) => { const test = convertExpressionToValue(expression.test, model, isBackConvert, changedModel); const consequent = convertExpressionToValue(expression.consequent, model, isBackConvert, changedModel); const alternate = convertExpressionToValue(expression.alternate, model, isBackConvert, changedModel); return test ? consequent : alternate; }, - Identifier: (expression, model, isBackConvert, changedModel) => { + 'Identifier': (expression, model, isBackConvert, changedModel) => { const context = changedModel[expression.name] ? changedModel : model; return getValueWithContext(expression.name, context); }, - Literal: (expression, model, isBackConvert, changedModel) => { + 'Literal': (expression, model, isBackConvert, changedModel) => { return expression.value; }, - LogicalExpression: (expression, model, isBackConvert, changedModel) => { + 'LogicalExpression': (expression, model, isBackConvert, changedModel) => { if (!leftRightOperators[expression.operator]) { throw Error('Disallowed operator: ' + expression.operator); } @@ -97,12 +98,12 @@ const expressionParsers = { return leftRightOperators[expression.operator](left, right); }, - MemberExpression: (expression, model, isBackConvert, changedModel) => { + 'MemberExpression': (expression, model, isBackConvert, changedModel) => { const object = convertExpressionToValue(expression.object, model, isBackConvert, changedModel); const property = convertExpressionToValue(expression.property, object, isBackConvert, object); return expression.computed ? getValueWithContext(property, object) : property; }, - NewExpression: (expression, model, isBackConvert, changedModel) => { + 'NewExpression': (expression, model, isBackConvert, changedModel) => { const callback = convertExpressionToValue(expression.callee, model, isBackConvert, changedModel); const parsedArgs = []; for (let argument of expression.arguments) { @@ -111,7 +112,7 @@ const expressionParsers = { } return new callback(...parsedArgs); }, - ObjectExpression: (expression, model, isBackConvert, changedModel) => { + 'ObjectExpression': (expression, model, isBackConvert, changedModel) => { const parsed = {}; for (let property of expression.properties) { const key = convertExpressionToValue(expression.key, model, isBackConvert, changedModel); @@ -120,14 +121,14 @@ const expressionParsers = { } return parsed; }, - SpreadElement: (expression, model, isBackConvert, changedModel) => { + 'SpreadElement': (expression, model, isBackConvert, changedModel) => { const argument = convertExpressionToValue(expression.argument, model, isBackConvert, changedModel); return argument; }, - TemplateElement: (expression, model, isBackConvert, changedModel) => { + 'TemplateElement': (expression, model, isBackConvert, changedModel) => { return expression.value.cooked; }, - TemplateLiteral: (expression, model, isBackConvert, changedModel) => { + 'TemplateLiteral': (expression, model, isBackConvert, changedModel) => { let parsedText = ''; for (let q of expression.quasis) { parsedText += convertExpressionToValue(q, model, isBackConvert, changedModel); @@ -138,7 +139,7 @@ const expressionParsers = { } return parsedText; }, - UnaryExpression: (expression, model, isBackConvert, changedModel) => { + 'UnaryExpression': (expression, model, isBackConvert, changedModel) => { if (!unaryOperators[expression.operator]) { throw Error('Disallowed operator: ' + expression.operator); } From 745c04a9bb6c102fc3c9c543c9bd02854478b261 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Sun, 2 Jan 2022 15:57:00 +0000 Subject: [PATCH 13/33] chore: Improved checks and added support for `void` and `typeof`. --- .../ui/core/bindable/bindable-expressions.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index c9c3da9c00..f6b1c497c7 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -3,10 +3,13 @@ import { isFunction, isNullOrUndefined, isObject } from '../../../utils/types'; const expressionsCache = {}; +// prettier-ignore const unaryOperators = { '+': (v) => +v, '-': (v) => -v, '!': (v) => !v, + 'void': (v) => void v, + 'typeof': (v) => typeof v }; // prettier-ignore @@ -29,7 +32,7 @@ const leftRightOperators = { '||': (l, r) => l || r, '??': (l, r) => l ?? r, 'in': (l, r) => l in r, - 'instanceof': (l, r) => l instanceof r, + 'instanceof': (l, r) => l instanceof r }; // prettier-ignore @@ -43,7 +46,7 @@ const expressionParsers = { return parsed; }, 'BinaryExpression': (expression, model, isBackConvert, changedModel) => { - if (!leftRightOperators[expression.operator]) { + if (leftRightOperators[expression.operator] == null) { throw new Error('Disallowed operator: ' + expression.operator); } @@ -51,7 +54,7 @@ const expressionParsers = { const right = convertExpressionToValue(expression.right, model, isBackConvert, changedModel); if (expression.operator == '|') { - if (right.converterArgs) { + if (isFunction(right) && right.converterArgs != null) { return right(left, ...right.converterArgs); } throw new Error('Invalid converter after ' + expression.operator + ' operator'); @@ -89,7 +92,7 @@ const expressionParsers = { return expression.value; }, 'LogicalExpression': (expression, model, isBackConvert, changedModel) => { - if (!leftRightOperators[expression.operator]) { + if (leftRightOperators[expression.operator] == null) { throw Error('Disallowed operator: ' + expression.operator); } @@ -140,18 +143,18 @@ const expressionParsers = { return parsedText; }, 'UnaryExpression': (expression, model, isBackConvert, changedModel) => { - if (!unaryOperators[expression.operator]) { + if (unaryOperators[expression.operator] == null) { throw Error('Disallowed operator: ' + expression.operator); } const argument = convertExpressionToValue(expression.argument, model, isBackConvert, changedModel); return unaryOperators[expression.operator](argument); - }, + } }; function getConverterCallback(context, args, isBackConvert) { let callback = isBackConvert ? context.toModel : context.toView; - if (!callback) { + if (callback == null) { callback = Function.prototype; } callback = callback.bind(context); @@ -169,7 +172,7 @@ function getValueWithContext(key, context) { export function parseExpression(expressionText) { let expression = expressionsCache[expressionText]; - if (!expression) { + if (expression == null) { let syntax = parse(expressionText); let statements = syntax.body; for (let statement of statements) { From 7b2c7c710bb9df54e7ed9ac6024cccd6f05e55bf Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Sun, 2 Jan 2022 16:00:37 +0000 Subject: [PATCH 14/33] fix: Regex literal was not handled. --- packages/core/ui/core/bindable/bindable-expressions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index f6b1c497c7..c279618130 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -89,7 +89,7 @@ const expressionParsers = { return getValueWithContext(expression.name, context); }, 'Literal': (expression, model, isBackConvert, changedModel) => { - return expression.value; + return expression.regex != null ? new RegExp(expression.regex.pattern, expression.regex.flags) : expression.value; }, 'LogicalExpression': (expression, model, isBackConvert, changedModel) => { if (leftRightOperators[expression.operator] == null) { From 71a1db7e205e84ece8dd42bdfbbfa4013238c95e Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Sun, 2 Jan 2022 16:07:33 +0000 Subject: [PATCH 15/33] chore: Added ts types. --- .../ui/core/bindable/bindable-expressions.ts | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index c279618130..19c79bf1f9 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -37,7 +37,7 @@ const leftRightOperators = { // prettier-ignore const expressionParsers = { - 'ArrayExpression': (expression, model, isBackConvert, changedModel) => { + 'ArrayExpression': (expression, model, isBackConvert: boolean, changedModel) => { const parsed = []; for (let element of expression.elements) { let value = convertExpressionToValue(element, model, isBackConvert, changedModel); @@ -45,7 +45,7 @@ const expressionParsers = { } return parsed; }, - 'BinaryExpression': (expression, model, isBackConvert, changedModel) => { + 'BinaryExpression': (expression, model, isBackConvert: boolean, changedModel) => { if (leftRightOperators[expression.operator] == null) { throw new Error('Disallowed operator: ' + expression.operator); } @@ -62,7 +62,7 @@ const expressionParsers = { return leftRightOperators[expression.operator](left, right); }, - 'CallExpression': (expression, model, isBackConvert, changedModel) => { + 'CallExpression': (expression, model, isBackConvert: boolean, changedModel) => { const callback = convertExpressionToValue(expression.callee, model, isBackConvert, changedModel); const isConverter = isObject(callback) && (isFunction(callback.toModel) || isFunction(callback.toView)); @@ -78,20 +78,20 @@ const expressionParsers = { return isConverter ? getConverterCallback(callback, parsedArgs, isBackConvert) : callback(...parsedArgs); }, - 'ConditionalExpression': (expression, model, isBackConvert, changedModel) => { + 'ConditionalExpression': (expression, model, isBackConvert: boolean, changedModel) => { const test = convertExpressionToValue(expression.test, model, isBackConvert, changedModel); const consequent = convertExpressionToValue(expression.consequent, model, isBackConvert, changedModel); const alternate = convertExpressionToValue(expression.alternate, model, isBackConvert, changedModel); return test ? consequent : alternate; }, - 'Identifier': (expression, model, isBackConvert, changedModel) => { + 'Identifier': (expression, model, isBackConvert: boolean, changedModel) => { const context = changedModel[expression.name] ? changedModel : model; return getValueWithContext(expression.name, context); }, - 'Literal': (expression, model, isBackConvert, changedModel) => { + 'Literal': (expression, model, isBackConvert: boolean, changedModel) => { return expression.regex != null ? new RegExp(expression.regex.pattern, expression.regex.flags) : expression.value; }, - 'LogicalExpression': (expression, model, isBackConvert, changedModel) => { + 'LogicalExpression': (expression, model, isBackConvert: boolean, changedModel) => { if (leftRightOperators[expression.operator] == null) { throw Error('Disallowed operator: ' + expression.operator); } @@ -101,12 +101,12 @@ const expressionParsers = { return leftRightOperators[expression.operator](left, right); }, - 'MemberExpression': (expression, model, isBackConvert, changedModel) => { + 'MemberExpression': (expression, model, isBackConvert: boolean, changedModel) => { const object = convertExpressionToValue(expression.object, model, isBackConvert, changedModel); const property = convertExpressionToValue(expression.property, object, isBackConvert, object); return expression.computed ? getValueWithContext(property, object) : property; }, - 'NewExpression': (expression, model, isBackConvert, changedModel) => { + 'NewExpression': (expression, model, isBackConvert: boolean, changedModel) => { const callback = convertExpressionToValue(expression.callee, model, isBackConvert, changedModel); const parsedArgs = []; for (let argument of expression.arguments) { @@ -115,7 +115,7 @@ const expressionParsers = { } return new callback(...parsedArgs); }, - 'ObjectExpression': (expression, model, isBackConvert, changedModel) => { + 'ObjectExpression': (expression, model, isBackConvert: boolean, changedModel) => { const parsed = {}; for (let property of expression.properties) { const key = convertExpressionToValue(expression.key, model, isBackConvert, changedModel); @@ -124,14 +124,14 @@ const expressionParsers = { } return parsed; }, - 'SpreadElement': (expression, model, isBackConvert, changedModel) => { + 'SpreadElement': (expression, model, isBackConvert: boolean, changedModel) => { const argument = convertExpressionToValue(expression.argument, model, isBackConvert, changedModel); return argument; }, - 'TemplateElement': (expression, model, isBackConvert, changedModel) => { + 'TemplateElement': (expression, model, isBackConvert: boolean, changedModel) => { return expression.value.cooked; }, - 'TemplateLiteral': (expression, model, isBackConvert, changedModel) => { + 'TemplateLiteral': (expression, model, isBackConvert: boolean, changedModel) => { let parsedText = ''; for (let q of expression.quasis) { parsedText += convertExpressionToValue(q, model, isBackConvert, changedModel); @@ -142,7 +142,7 @@ const expressionParsers = { } return parsedText; }, - 'UnaryExpression': (expression, model, isBackConvert, changedModel) => { + 'UnaryExpression': (expression, model, isBackConvert: boolean, changedModel) => { if (unaryOperators[expression.operator] == null) { throw Error('Disallowed operator: ' + expression.operator); } @@ -152,7 +152,7 @@ const expressionParsers = { } }; -function getConverterCallback(context, args, isBackConvert) { +function getConverterCallback(context, args, isBackConvert: boolean) { let callback = isBackConvert ? context.toModel : context.toView; if (callback == null) { callback = Function.prototype; @@ -170,7 +170,7 @@ function getValueWithContext(key, context) { return value; } -export function parseExpression(expressionText) { +export function parseExpression(expressionText: string) { let expression = expressionsCache[expressionText]; if (expression == null) { let syntax = parse(expressionText); @@ -186,6 +186,6 @@ export function parseExpression(expressionText) { return expression; } -export function convertExpressionToValue(expression, model, isBackConvert, changedModel) { +export function convertExpressionToValue(expression, model, isBackConvert: boolean, changedModel) { return expressionParsers[expression.type](expression, model, isBackConvert, changedModel); } From 06965ccbca50e7cf26d414d533a9987416d89097 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Sun, 2 Jan 2022 16:34:04 +0000 Subject: [PATCH 16/33] chore: Added `ASTExpression` type. --- .../ui/core/bindable/bindable-expressions.ts | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index 19c79bf1f9..18586d5af1 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -1,6 +1,11 @@ import { parse } from 'esprima'; import { isFunction, isNullOrUndefined, isObject } from '../../../utils/types'; +interface ASTExpression { + readonly type: string; + readonly [prop: string]: any; +} + const expressionsCache = {}; // prettier-ignore @@ -37,7 +42,7 @@ const leftRightOperators = { // prettier-ignore const expressionParsers = { - 'ArrayExpression': (expression, model, isBackConvert: boolean, changedModel) => { + 'ArrayExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const parsed = []; for (let element of expression.elements) { let value = convertExpressionToValue(element, model, isBackConvert, changedModel); @@ -45,7 +50,7 @@ const expressionParsers = { } return parsed; }, - 'BinaryExpression': (expression, model, isBackConvert: boolean, changedModel) => { + 'BinaryExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { if (leftRightOperators[expression.operator] == null) { throw new Error('Disallowed operator: ' + expression.operator); } @@ -62,7 +67,7 @@ const expressionParsers = { return leftRightOperators[expression.operator](left, right); }, - 'CallExpression': (expression, model, isBackConvert: boolean, changedModel) => { + 'CallExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const callback = convertExpressionToValue(expression.callee, model, isBackConvert, changedModel); const isConverter = isObject(callback) && (isFunction(callback.toModel) || isFunction(callback.toView)); @@ -78,20 +83,20 @@ const expressionParsers = { return isConverter ? getConverterCallback(callback, parsedArgs, isBackConvert) : callback(...parsedArgs); }, - 'ConditionalExpression': (expression, model, isBackConvert: boolean, changedModel) => { + 'ConditionalExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const test = convertExpressionToValue(expression.test, model, isBackConvert, changedModel); const consequent = convertExpressionToValue(expression.consequent, model, isBackConvert, changedModel); const alternate = convertExpressionToValue(expression.alternate, model, isBackConvert, changedModel); return test ? consequent : alternate; }, - 'Identifier': (expression, model, isBackConvert: boolean, changedModel) => { + 'Identifier': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const context = changedModel[expression.name] ? changedModel : model; return getValueWithContext(expression.name, context); }, - 'Literal': (expression, model, isBackConvert: boolean, changedModel) => { + 'Literal': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { return expression.regex != null ? new RegExp(expression.regex.pattern, expression.regex.flags) : expression.value; }, - 'LogicalExpression': (expression, model, isBackConvert: boolean, changedModel) => { + 'LogicalExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { if (leftRightOperators[expression.operator] == null) { throw Error('Disallowed operator: ' + expression.operator); } @@ -101,12 +106,12 @@ const expressionParsers = { return leftRightOperators[expression.operator](left, right); }, - 'MemberExpression': (expression, model, isBackConvert: boolean, changedModel) => { + 'MemberExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const object = convertExpressionToValue(expression.object, model, isBackConvert, changedModel); const property = convertExpressionToValue(expression.property, object, isBackConvert, object); return expression.computed ? getValueWithContext(property, object) : property; }, - 'NewExpression': (expression, model, isBackConvert: boolean, changedModel) => { + 'NewExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const callback = convertExpressionToValue(expression.callee, model, isBackConvert, changedModel); const parsedArgs = []; for (let argument of expression.arguments) { @@ -115,7 +120,7 @@ const expressionParsers = { } return new callback(...parsedArgs); }, - 'ObjectExpression': (expression, model, isBackConvert: boolean, changedModel) => { + 'ObjectExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const parsed = {}; for (let property of expression.properties) { const key = convertExpressionToValue(expression.key, model, isBackConvert, changedModel); @@ -124,14 +129,14 @@ const expressionParsers = { } return parsed; }, - 'SpreadElement': (expression, model, isBackConvert: boolean, changedModel) => { + 'SpreadElement': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const argument = convertExpressionToValue(expression.argument, model, isBackConvert, changedModel); return argument; }, - 'TemplateElement': (expression, model, isBackConvert: boolean, changedModel) => { + 'TemplateElement': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { return expression.value.cooked; }, - 'TemplateLiteral': (expression, model, isBackConvert: boolean, changedModel) => { + 'TemplateLiteral': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { let parsedText = ''; for (let q of expression.quasis) { parsedText += convertExpressionToValue(q, model, isBackConvert, changedModel); @@ -142,7 +147,7 @@ const expressionParsers = { } return parsedText; }, - 'UnaryExpression': (expression, model, isBackConvert: boolean, changedModel) => { + 'UnaryExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { if (unaryOperators[expression.operator] == null) { throw Error('Disallowed operator: ' + expression.operator); } @@ -170,7 +175,7 @@ function getValueWithContext(key, context) { return value; } -export function parseExpression(expressionText: string) { +export function parseExpression(expressionText: string): ASTExpression { let expression = expressionsCache[expressionText]; if (expression == null) { let syntax = parse(expressionText); @@ -186,6 +191,6 @@ export function parseExpression(expressionText: string) { return expression; } -export function convertExpressionToValue(expression, model, isBackConvert: boolean, changedModel) { +export function convertExpressionToValue(expression: ASTExpression, model, isBackConvert: boolean, changedModel) { return expressionParsers[expression.type](expression, model, isBackConvert, changedModel); } From b4398a88059926705a1869c58a336b1e9c22049a Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Sun, 2 Jan 2022 17:52:17 +0000 Subject: [PATCH 17/33] fix: Conditional and Logical expressions calculated `right` value when they should not. --- .../ui/core/bindable/bindable-expressions.ts | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index 18586d5af1..b3a40e70e8 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -18,7 +18,7 @@ const unaryOperators = { }; // prettier-ignore -const leftRightOperators = { +const binaryOperators = { '+': (l, r) => l + r, '-': (l, r) => l - r, '*': (l, r) => l * r, @@ -32,14 +32,18 @@ const leftRightOperators = { '!=': (l, r) => l != r, '===': (l, r) => l === r, '!==': (l, r) => l !== r, - '&&': (l, r) => l && r, '|': (l, r) => l | r, - '||': (l, r) => l || r, - '??': (l, r) => l ?? r, 'in': (l, r) => l in r, 'instanceof': (l, r) => l instanceof r }; +// prettier-ignore +const logicalOperators = { + '&&': (l, r) => l && r(), + '||': (l, r) => l || r(), + '??': (l, r) => l ?? r() +}; + // prettier-ignore const expressionParsers = { 'ArrayExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { @@ -51,8 +55,8 @@ const expressionParsers = { return parsed; }, 'BinaryExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { - if (leftRightOperators[expression.operator] == null) { - throw new Error('Disallowed operator: ' + expression.operator); + if (binaryOperators[expression.operator] == null) { + throw new Error('Disallowed binary operator: ' + expression.operator); } const left = convertExpressionToValue(expression.left, model, isBackConvert, changedModel); @@ -65,7 +69,7 @@ const expressionParsers = { throw new Error('Invalid converter after ' + expression.operator + ' operator'); } - return leftRightOperators[expression.operator](left, right); + return binaryOperators[expression.operator](left, right); }, 'CallExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const callback = convertExpressionToValue(expression.callee, model, isBackConvert, changedModel); @@ -85,9 +89,7 @@ const expressionParsers = { }, 'ConditionalExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const test = convertExpressionToValue(expression.test, model, isBackConvert, changedModel); - const consequent = convertExpressionToValue(expression.consequent, model, isBackConvert, changedModel); - const alternate = convertExpressionToValue(expression.alternate, model, isBackConvert, changedModel); - return test ? consequent : alternate; + return convertExpressionToValue(expression[test ? 'consequent' : 'alternate'], model, isBackConvert, changedModel); }, 'Identifier': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const context = changedModel[expression.name] ? changedModel : model; @@ -97,14 +99,11 @@ const expressionParsers = { return expression.regex != null ? new RegExp(expression.regex.pattern, expression.regex.flags) : expression.value; }, 'LogicalExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { - if (leftRightOperators[expression.operator] == null) { - throw Error('Disallowed operator: ' + expression.operator); + if (logicalOperators[expression.operator] == null) { + throw new Error('Disallowed logical operator: ' + expression.operator); } - const left = convertExpressionToValue(expression.left, model, isBackConvert, changedModel); - const right = convertExpressionToValue(expression.right, model, isBackConvert, changedModel); - - return leftRightOperators[expression.operator](left, right); + return logicalOperators[expression.operator](left, () => convertExpressionToValue(expression.right, model, isBackConvert, changedModel)); }, 'MemberExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const object = convertExpressionToValue(expression.object, model, isBackConvert, changedModel); @@ -149,7 +148,7 @@ const expressionParsers = { }, 'UnaryExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { if (unaryOperators[expression.operator] == null) { - throw Error('Disallowed operator: ' + expression.operator); + throw Error('Disallowed unary operator: ' + expression.operator); } const argument = convertExpressionToValue(expression.argument, model, isBackConvert, changedModel); From 261c2a607d0d92bb0534b9694a93e271bf4d9b34 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Sun, 2 Jan 2022 18:37:13 +0000 Subject: [PATCH 18/33] ref: Improved converters mechanism. --- .../core/ui/core/bindable/bindable-expressions.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index b3a40e70e8..77b7a1caad 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -63,8 +63,9 @@ const expressionParsers = { const right = convertExpressionToValue(expression.right, model, isBackConvert, changedModel); if (expression.operator == '|') { - if (isFunction(right) && right.converterArgs != null) { - return right(left, ...right.converterArgs); + if (isFunction(right.callback) && right.context != null && right.args != null) { + right.args.unshift(left); + return right.apply(right.context, right.args); } throw new Error('Invalid converter after ' + expression.operator + ' operator'); } @@ -85,7 +86,7 @@ const expressionParsers = { throw new Error('Cannot perform a call using a non-function property'); } - return isConverter ? getConverterCallback(callback, parsedArgs, isBackConvert) : callback(...parsedArgs); + return isConverter ? getConverter(callback, parsedArgs, isBackConvert) : callback(...parsedArgs); }, 'ConditionalExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const test = convertExpressionToValue(expression.test, model, isBackConvert, changedModel); @@ -156,14 +157,14 @@ const expressionParsers = { } }; -function getConverterCallback(context, args, isBackConvert: boolean) { +function getConverter(context, args, isBackConvert: boolean) { + const converter = { callback: null, context, args }; let callback = isBackConvert ? context.toModel : context.toView; if (callback == null) { callback = Function.prototype; } - callback = callback.bind(context); - callback.converterArgs = args; - return callback; + converter.callback = callback; + return converter; } function getValueWithContext(key, context) { From 7a338f052abffdd346a2a53bef0fd6e83b3c05cb Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Sun, 2 Jan 2022 18:55:47 +0000 Subject: [PATCH 19/33] fix: Coverters were broken on previous commit. --- packages/core/ui/core/bindable/bindable-expressions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index 77b7a1caad..db5347d161 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -63,9 +63,9 @@ const expressionParsers = { const right = convertExpressionToValue(expression.right, model, isBackConvert, changedModel); if (expression.operator == '|') { - if (isFunction(right.callback) && right.context != null && right.args != null) { + if (right != null && isFunction(right.callback) && right.context != null && right.args != null) { right.args.unshift(left); - return right.apply(right.context, right.args); + return right.callback.apply(right.context, right.args); } throw new Error('Invalid converter after ' + expression.operator + ' operator'); } From 3e1737589f1a76fc01db33ec324f84852f29ffa6 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Mon, 3 Jan 2022 00:12:19 +0000 Subject: [PATCH 20/33] feat: Added support for optional chaining. --- .../ui/core/bindable/bindable-expressions.ts | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index db5347d161..2862169c98 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -73,7 +73,16 @@ const expressionParsers = { return binaryOperators[expression.operator](left, right); }, 'CallExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { - const callback = convertExpressionToValue(expression.callee, model, isBackConvert, changedModel); + let object; + let property; + if (expression.callee.type == 'MemberExpression') { + property = convertExpressionToValue(expression.callee.property, model, isBackConvert, changedModel); + object = convertExpressionToValue(expression.callee.object, model, isBackConvert, changedModel); + } else { + property = expression.callee.name; + object = getContext(property, model, changedModel); + } + const callback = object?.[property]; const isConverter = isObject(callback) && (isFunction(callback.toModel) || isFunction(callback.toView)); const parsedArgs = []; @@ -86,15 +95,18 @@ const expressionParsers = { throw new Error('Cannot perform a call using a non-function property'); } - return isConverter ? getConverter(callback, parsedArgs, isBackConvert) : callback(...parsedArgs); + return isConverter ? getConverter(callback, parsedArgs, isBackConvert) : callback.apply(object, parsedArgs); + }, + 'ChainExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { + return convertExpressionToValue(expression.expression, model, isBackConvert, changedModel); }, 'ConditionalExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const test = convertExpressionToValue(expression.test, model, isBackConvert, changedModel); return convertExpressionToValue(expression[test ? 'consequent' : 'alternate'], model, isBackConvert, changedModel); }, 'Identifier': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { - const context = changedModel[expression.name] ? changedModel : model; - return getValueWithContext(expression.name, context); + const context = getContext(expression.name, model, changedModel); + return context[expression.name]; }, 'Literal': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { return expression.regex != null ? new RegExp(expression.regex.pattern, expression.regex.flags) : expression.value; @@ -108,8 +120,8 @@ const expressionParsers = { }, 'MemberExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const object = convertExpressionToValue(expression.object, model, isBackConvert, changedModel); - const property = convertExpressionToValue(expression.property, object, isBackConvert, object); - return expression.computed ? getValueWithContext(property, object) : property; + const property = expression.property.type == 'Identifier' ? expression.property.name : convertExpressionToValue(expression.property, object, isBackConvert, object); + return expression.optional ? object?.[property] : object[property]; }, 'NewExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const callback = convertExpressionToValue(expression.callee, model, isBackConvert, changedModel); @@ -157,6 +169,10 @@ const expressionParsers = { } }; +function getContext(key, model, changedModel) { + return key in changedModel ? changedModel : model; +} + function getConverter(context, args, isBackConvert: boolean) { const converter = { callback: null, context, args }; let callback = isBackConvert ? context.toModel : context.toView; @@ -167,14 +183,6 @@ function getConverter(context, args, isBackConvert: boolean) { return converter; } -function getValueWithContext(key, context) { - let value = context[key]; - if (isFunction(value)) { - value = value.bind(context); - } - return value; -} - export function parseExpression(expressionText: string): ASTExpression { let expression = expressionsCache[expressionText]; if (expression == null) { From 4eaf0beb197c7272a3db0bc7b4a65d5d444bfb9f Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Mon, 3 Jan 2022 11:07:44 +0000 Subject: [PATCH 21/33] fix: Object expression did not parse keys properly if they contained spreads. --- .../ui/core/bindable/bindable-expressions.ts | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index 2862169c98..8977cf4fb9 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -50,7 +50,7 @@ const expressionParsers = { const parsed = []; for (let element of expression.elements) { let value = convertExpressionToValue(element, model, isBackConvert, changedModel); - element.type === 'SpreadElement' ? parsed.push(...value) : parsed.push(value); + element.type == 'SpreadElement' ? parsed.push(...value) : parsed.push(value); } return parsed; }, @@ -82,13 +82,13 @@ const expressionParsers = { property = expression.callee.name; object = getContext(property, model, changedModel); } - const callback = object?.[property]; + const callback = expression.callee.optional ? object?.[property] : object[property]; const isConverter = isObject(callback) && (isFunction(callback.toModel) || isFunction(callback.toView)); const parsedArgs = []; for (let argument of expression.arguments) { let value = convertExpressionToValue(argument, model, isBackConvert, changedModel); - argument.type === 'SpreadElement' ? parsedArgs.push(...value) : parsedArgs.push(value); + argument.type == 'SpreadElement' ? parsedArgs.push(...value) : parsedArgs.push(value); } if (isNullOrUndefined(callback) || (!isFunction(callback) && !isConverter)) { @@ -128,18 +128,22 @@ const expressionParsers = { const parsedArgs = []; for (let argument of expression.arguments) { let value = convertExpressionToValue(argument, model, isBackConvert, changedModel); - argument.type === 'SpreadElement' ? parsedArgs.push(...value) : parsedArgs.push(value); + argument.type == 'SpreadElement' ? parsedArgs.push(...value) : parsedArgs.push(value); } return new callback(...parsedArgs); }, 'ObjectExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { - const parsed = {}; + const parsedObject = {}; for (let property of expression.properties) { - const key = convertExpressionToValue(expression.key, model, isBackConvert, changedModel); - const value = convertExpressionToValue(expression.value, model, isBackConvert, changedModel); - parsed[key] = value; + const value = convertExpressionToValue(property, model, isBackConvert, changedModel); + Object.assign(parsedObject, value); } - return parsed; + return parsedObject; + }, + 'Property': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { + const key = convertExpressionToValue(expression.key, model, isBackConvert, changedModel); + const value = convertExpressionToValue(expression.value, model, isBackConvert, changedModel); + return {[key]: value}; }, 'SpreadElement': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const argument = convertExpressionToValue(expression.argument, model, isBackConvert, changedModel); From 221c2c95da5ff640bfb939a37db371466a5467e1 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Mon, 3 Jan 2022 11:42:07 +0000 Subject: [PATCH 22/33] chore: Using `acorn` parser as it's used from NS webpack and supports latest ecma versions. --- packages/core/package.json | 2 +- packages/core/ui/core/bindable/bindable-expressions.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index eba3dd0886..fa8d363041 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -29,8 +29,8 @@ }, "dependencies": { "@nativescript/hook": "~2.0.0", + "acorn": "^8.7.0", "css-tree": "^1.1.2", - "esprima": "^4.0.1", "reduce-css-calc": "^2.1.7", "tslib": "~2.0.0" }, diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index 8977cf4fb9..ae269221d4 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -1,4 +1,4 @@ -import { parse } from 'esprima'; +import { parse } from 'acorn'; import { isFunction, isNullOrUndefined, isObject } from '../../../utils/types'; interface ASTExpression { @@ -141,7 +141,7 @@ const expressionParsers = { return parsedObject; }, 'Property': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { - const key = convertExpressionToValue(expression.key, model, isBackConvert, changedModel); + const key = expression.computed ? convertExpressionToValue(expression.key, model, isBackConvert, changedModel) : expression.key?.name; const value = convertExpressionToValue(expression.value, model, isBackConvert, changedModel); return {[key]: value}; }, @@ -190,8 +190,8 @@ function getConverter(context, args, isBackConvert: boolean) { export function parseExpression(expressionText: string): ASTExpression { let expression = expressionsCache[expressionText]; if (expression == null) { - let syntax = parse(expressionText); - let statements = syntax.body; + const program: any = parse(expressionText, { ecmaVersion: 'latest' }); + const statements = program.body; for (let statement of statements) { if (statement.type == 'ExpressionStatement') { expression = statement.expression; From 0c810559962ad1be6acf5f5cc074d0f932864f69 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Mon, 3 Jan 2022 15:00:15 +0000 Subject: [PATCH 23/33] fix: Non-computed properties were broken in the case of CallExpression. --- packages/core/ui/core/bindable/bindable-expressions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index ae269221d4..1f06c90cd6 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -76,7 +76,7 @@ const expressionParsers = { let object; let property; if (expression.callee.type == 'MemberExpression') { - property = convertExpressionToValue(expression.callee.property, model, isBackConvert, changedModel); + property = expression.callee.computed ? convertExpressionToValue(expression.callee.property, model, isBackConvert, changedModel) : expression.callee.property?.name; object = convertExpressionToValue(expression.callee.object, model, isBackConvert, changedModel); } else { property = expression.callee.name; From 1eb31a1f874983bdfdc1b0f30ec1bba64b658858 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Mon, 3 Jan 2022 15:16:08 +0000 Subject: [PATCH 24/33] chore: Improved computed property check in `MemberExpression`. --- packages/core/ui/core/bindable/bindable-expressions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index 1f06c90cd6..be44777f57 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -120,7 +120,7 @@ const expressionParsers = { }, 'MemberExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { const object = convertExpressionToValue(expression.object, model, isBackConvert, changedModel); - const property = expression.property.type == 'Identifier' ? expression.property.name : convertExpressionToValue(expression.property, object, isBackConvert, object); + const property = expression.computed ? convertExpressionToValue(expression.property, object, isBackConvert, object) : expression.property?.name; return expression.optional ? object?.[property] : object[property]; }, 'NewExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => { From 191cae8ee9d53fd64d36c81ef2790d1b0f194eaa Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Wed, 5 Jan 2022 22:34:03 +0000 Subject: [PATCH 25/33] chore: Changed `acorn` ecma version since it's not supported by acorn from NS Webpack. --- packages/core/ui/core/bindable/bindable-expressions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/ui/core/bindable/bindable-expressions.ts b/packages/core/ui/core/bindable/bindable-expressions.ts index be44777f57..91ad7020aa 100644 --- a/packages/core/ui/core/bindable/bindable-expressions.ts +++ b/packages/core/ui/core/bindable/bindable-expressions.ts @@ -190,7 +190,7 @@ function getConverter(context, args, isBackConvert: boolean) { export function parseExpression(expressionText: string): ASTExpression { let expression = expressionsCache[expressionText]; if (expression == null) { - const program: any = parse(expressionText, { ecmaVersion: 'latest' }); + const program: any = parse(expressionText, { ecmaVersion: 2020 }); const statements = program.body; for (let statement of statements) { if (statement.type == 'ExpressionStatement') { From 2f7627d38273efa9776a2b6d850bc9961c9a45d4 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Wed, 5 Jan 2022 23:23:51 +0000 Subject: [PATCH 26/33] test: Corrected test regarding converters. --- apps/automated/src/ui/core/bindable/bindable-tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/automated/src/ui/core/bindable/bindable-tests.ts b/apps/automated/src/ui/core/bindable/bindable-tests.ts index 10e6b6edb3..f471b6e72d 100644 --- a/apps/automated/src/ui/core/bindable/bindable-tests.ts +++ b/apps/automated/src/ui/core/bindable/bindable-tests.ts @@ -669,7 +669,7 @@ export function test_BindingConverterCalledEvenWithNullValue() { const testFunc = function (views: Array) { const testLabel =