feat: new expression parser for xml bindings (#9729)

This commit is contained in:
Dimitris - Rafail Katsampas
2022-01-06 19:04:54 +02:00
committed by Nathan Walker
parent a518249958
commit 90ceed15d3
17 changed files with 310 additions and 2496 deletions

View File

@ -659,17 +659,19 @@ export function test_BindingConverterCalledEvenWithNullValue() {
const testPropertyValue = null;
const expectedValue = 'collapsed';
pageViewModel.set('testProperty', testPropertyValue);
appModule.getResources()['converter'] = function (value) {
if (value) {
return 'visible';
} else {
return 'collapsed';
}
appModule.getResources()['converter'] = {
toView: function (value) {
if (value) {
return 'visible';
} else {
return 'collapsed';
}
},
};
const testFunc = function (views: Array<View>) {
const testLabel = <Label>views[0];
testLabel.bind({ sourceProperty: 'testProperty', targetProperty: 'text', expression: 'testProperty | converter' });
testLabel.bind({ sourceProperty: 'testProperty', targetProperty: 'text', expression: 'testProperty | converter()' });
const page = <Page>views[1];
page.bindingContext = pageViewModel;

View File

@ -437,15 +437,17 @@ export class ListViewTest extends UITest<ListView> {
public test_usingAppLevelConvertersInListViewItems() {
var listView = this.testView;
var dateConverter = function (value, format) {
var result = format;
var day = value.getDate();
result = result.replace('DD', day < 10 ? '0' + day : day);
var month = value.getMonth() + 1;
result = result.replace('MM', month < 10 ? '0' + month : month);
result = result.replace('YYYY', value.getFullYear());
var dateConverter = {
toView: function (value, format) {
var result = format;
var day = value.getDate();
result = result.replace('DD', day < 10 ? '0' + day : day);
var month = value.getMonth() + 1;
result = result.replace('MM', month < 10 ? '0' + month : month);
result = result.replace('YYYY', value.getFullYear());
return result;
return result;
},
};
Application.getResources()['dateConverter'] = dateConverter;
@ -565,10 +567,12 @@ export class ListViewTest extends UITest<ListView> {
var listView = this.testView;
var converterCalledCounter = 0;
var testConverter = function (value) {
converterCalledCounter++;
var testConverter = {
toView: function (value) {
converterCalledCounter++;
return value;
return value;
},
};
Application.getResources()['testConverter'] = testConverter;
@ -578,7 +582,7 @@ export class ListViewTest extends UITest<ListView> {
listView.bindingContext = listViewModel;
listView.bind({ sourceProperty: 'items', targetProperty: 'items' });
listView.itemTemplate = '<Label id="testLabel" text="{{ $value, $value | testConverter }}" />';
listView.itemTemplate = '<Label id="testLabel" text="{{ $value, $value | testConverter() }}" />';
this.waitUntilListViewReady();
@ -589,10 +593,12 @@ export class ListViewTest extends UITest<ListView> {
var listView = this.testView;
var converterCalledCounter = 0;
var testConverter = function (value) {
converterCalledCounter++;
var testConverter = {
toView: function (value) {
converterCalledCounter++;
return value;
return value;
},
};
Application.getResources()['testConverter'] = testConverter;
@ -602,7 +608,7 @@ export class ListViewTest extends UITest<ListView> {
listView.bindingContext = listViewModel;
listView.bind({ sourceProperty: 'items', targetProperty: 'items' });
listView.itemTemplate = '<StackLayout><Label id="testLabel" text="{{ $value, $value | testConverter }}" /></StackLayout>';
listView.itemTemplate = '<StackLayout><Label id="testLabel" text="{{ $value, $value | testConverter() }}" /></StackLayout>';
this.waitUntilListViewReady();

View File

@ -252,15 +252,17 @@ export function test_splice_observable_array_refreshes_the_Repeater() {
export function test_usingAppLevelConvertersInRepeaterItems() {
var repeater = new Repeater();
var dateConverter = function (value, format) {
var result = format;
var day = value.getDate();
result = result.replace('DD', month < 10 ? '0' + day : day);
var month = value.getMonth() + 1;
result = result.replace('MM', month < 10 ? '0' + month : month);
result = result.replace('YYYY', value.getFullYear());
var dateConverter = {
toView: function (value, format) {
var result = format;
var day = value.getDate();
result = result.replace('DD', month < 10 ? '0' + day : day);
var month = value.getMonth() + 1;
result = result.replace('MM', month < 10 ? '0' + month : month);
result = result.replace('YYYY', value.getFullYear());
return result;
return result;
},
};
Application.getResources()['dateConverter'] = dateConverter;
@ -275,7 +277,7 @@ export function test_usingAppLevelConvertersInRepeaterItems() {
TKUnit.waitUntilReady(() => repeater.isLayoutValid);
TKUnit.assertEqual(getChildAtText(repeater, 0), dateConverter(new Date(), 'DD.MM.YYYY'), 'element');
TKUnit.assertEqual(getChildAtText(repeater, 0), dateConverter.toView(new Date(), 'DD.MM.YYYY'), 'element');
}
helper.buildUIAndRunTest(repeater, testAction);

View File

@ -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 <COPYRIGHT HOLDER> 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.

View File

@ -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).

View File

@ -1,266 +0,0 @@
/* tslint:disable */
// Type definitions for Esprima v1.2.0
// Project: http://esprima.org
// Definitions by: teppeis <https://github.com/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<Token>;
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<SomeStatement>, 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>, 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
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

View File

@ -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.

View File

@ -1,6 +0,0 @@
{
"sideEffects": false,
"name": "polymer-expressions",
"main": "polymer-expressions",
"types": "polymer-expressions.d.ts"
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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);

View File

@ -6,8 +6,8 @@
"version": "8.2.0-alpha.0",
"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",
"acorn": "^8.7.0",
"css-tree": "^1.1.2",
"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
}
]
}
}

View File

@ -0,0 +1,208 @@
import { parse } from 'acorn';
import { isFunction, isNullOrUndefined, isObject } from '../../../utils/types';
interface ASTExpression {
readonly type: string;
readonly [prop: string]: any;
}
const expressionsCache = {};
// prettier-ignore
const unaryOperators = {
'+': (v) => +v,
'-': (v) => -v,
'!': (v) => !v,
'void': (v) => void v,
'typeof': (v) => typeof v
};
// prettier-ignore
const binaryOperators = {
'+': (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
};
// 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) => {
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: ASTExpression, model, isBackConvert: boolean, changedModel) => {
if (binaryOperators[expression.operator] == null) {
throw new Error('Disallowed binary operator: ' + expression.operator);
}
const left = convertExpressionToValue(expression.left, model, isBackConvert, changedModel);
const right = convertExpressionToValue(expression.right, model, isBackConvert, changedModel);
if (expression.operator == '|') {
if (right != null && isFunction(right.callback) && right.context != null && right.args != null) {
right.args.unshift(left);
return right.callback.apply(right.context, right.args);
}
throw new Error('Invalid converter after ' + expression.operator + ' operator');
}
return binaryOperators[expression.operator](left, right);
},
'CallExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
let object;
let property;
if (expression.callee.type == 'MemberExpression') {
object = convertExpressionToValue(expression.callee.object, model, isBackConvert, changedModel);
property = expression.callee.computed ? convertExpressionToValue(expression.callee.property, model, isBackConvert, changedModel) : expression.callee.property?.name;
} else {
object = getContext(expression.callee.name, model, changedModel);
property = expression.callee?.name;
}
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);
}
if (isNullOrUndefined(callback) || (!isFunction(callback) && !isConverter)) {
throw new Error('Cannot perform a call using a non-function property');
}
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 = 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;
},
'LogicalExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
if (logicalOperators[expression.operator] == null) {
throw new Error('Disallowed logical operator: ' + expression.operator);
}
const left = convertExpressionToValue(expression.left, model, isBackConvert, changedModel);
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);
const property = expression.computed ? convertExpressionToValue(expression.property, model, isBackConvert, changedModel) : expression.property?.name;
return expression.optional ? object?.[property] : object[property];
},
'NewExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
const callback = convertExpressionToValue(expression.callee, model, isBackConvert, changedModel);
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: ASTExpression, model, isBackConvert: boolean, changedModel) => {
const parsedObject = {};
for (let property of expression.properties) {
const value = convertExpressionToValue(property, model, isBackConvert, changedModel);
Object.assign(parsedObject, value);
}
return parsedObject;
},
'Property': (expression: ASTExpression, model, isBackConvert: boolean, 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};
},
'SpreadElement': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
const argument = convertExpressionToValue(expression.argument, model, isBackConvert, changedModel);
return argument;
},
'TemplateElement': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
return expression.value.cooked;
},
'TemplateLiteral': (expression: ASTExpression, model, isBackConvert: boolean, 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: ASTExpression, model, isBackConvert: boolean, changedModel) => {
if (unaryOperators[expression.operator] == null) {
throw Error('Disallowed unary operator: ' + expression.operator);
}
const argument = convertExpressionToValue(expression.argument, model, isBackConvert, changedModel);
return unaryOperators[expression.operator](argument);
}
};
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;
if (callback == null) {
callback = Function.prototype;
}
converter.callback = callback;
return converter;
}
export function parseExpression(expressionText: string): ASTExpression {
let expression = expressionsCache[expressionText];
if (expression == null) {
const program: any = parse(expressionText, { ecmaVersion: 2020 });
const statements = program.body;
for (let statement of statements) {
if (statement.type == 'ExpressionStatement') {
expression = statement.expression;
break;
}
}
expressionsCache[expressionText] = expression;
}
return expression;
}
export function convertExpressionToValue(expression: ASTExpression, model, isBackConvert: boolean, changedModel) {
return expressionParsers[expression.type](expression, model, isBackConvert, changedModel);
}

View File

@ -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:
@ -360,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) {
@ -374,13 +373,21 @@ export class Binding {
}
private _getExpressionValue(expression: string, isBackConvert: boolean, changedModel: any): any {
let result: any = '';
if (!__UI_USE_EXTERNAL_RENDERER__) {
let context;
const addedProps = [];
try {
const exp = PolymerExpressions.getExpression(expression);
let exp;
try {
exp = parseExpression(expression);
} catch (e) {
result = e;
}
if (exp) {
const context = (this.source && this.source.get && this.source.get()) || global;
const model = {};
const addedProps = [];
context = (this.source && this.source.get && this.source.get()) || global;
const resources = bindableResources.get();
for (const prop in resources) {
if (resources.hasOwnProperty(prop) && !context.hasOwnProperty(prop)) {
@ -389,26 +396,27 @@ export class Binding {
}
}
this.prepareContextForExpression(context, expression, addedProps);
model[contextKey] = context;
const result = exp.getValue(model, isBackConvert, changedModel ? changedModel : model);
// clear added props
const addedPropsLength = addedProps.length;
for (let i = 0; i < addedPropsLength; i++) {
delete context[addedProps[i]];
// For expressions, there are also cases when binding must be updated after component is loaded (e.g. ListView)
if (this.prepareContextForExpression(context, expression, addedProps)) {
result = convertExpressionToValue(exp, context, isBackConvert, changedModel ? changedModel : context);
} else {
const targetInstance = this.target.get();
targetInstance.off('loaded', this.loadedHandlerVisualTreeBinding, this);
targetInstance.on('loaded', this.loadedHandlerVisualTreeBinding, this);
}
addedProps.length = 0;
return result;
}
return new Error(expression + ' is not a valid expression.');
} catch (e) {
const errorMessage = 'Run-time error occured in file: ' + e.sourceURL + ' at line: ' + e.line + ' and column: ' + e.column;
return new Error(errorMessage);
result = new Error(errorMessage);
}
// Clear added props
for (let prop of addedProps) {
delete context[prop];
}
addedProps.length = 0;
}
return result;
}
public onSourcePropertyChanged(data: PropertyChangeData) {
@ -472,24 +480,24 @@ export class Binding {
}
}
private prepareContextForExpression(model: Object, expression: string, newProps: Array<string>) {
private prepareContextForExpression(model: Object, expression: string, addedProps = []) {
const targetInstance = this.target.get();
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++) {
// 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;
@ -501,7 +509,7 @@ 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;
addedProps.push(bc.parentValueKey);
@ -509,13 +517,7 @@ export class Binding {
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;
}
private getSourcePropertyValue() {