Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
554 changes: 554 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"main": "./src/main.js",
"types": "./src/main.d.ts",
"scripts": {
"test": "tsc & mocha src/**/*.spec.js",
"test": "npm run test-es5 & npm run test-es2015",
"test-es5": "tsc -p src/test/tsconfig-es5.json & mocha src/**/*.spec.js",
"test-es2015": "tsc -p src/test/tsconfig-es2015.json & mocha src/**/*.spec.js",
"test-grep": "tsc & mocha src/**/*.spec.js --grep",
"test-single": "tsc & mocha src/**/*.spec.js --grep Mapper --debug-brk",
"showcase": "cd .\\showcase\\ npm run start-showcase",
Expand Down Expand Up @@ -36,6 +38,8 @@
"chai": "^4.1.2",
"mocha": "^5.0.0",
"ts-node": "^4.1.0",
"typescript": "2.6.2"
}
"tslint": "^5.9.1",
"typescript": "3.0.3"
},
"dependencies": {}
}
4 changes: 2 additions & 2 deletions showcase/src/models/reference-model-test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export class ReferencesModelTest {
public id: number;
public name: string;
public id!: number;
public name: string | undefined;
}
248 changes: 145 additions & 103 deletions src/expression-utils.ts
Original file line number Diff line number Diff line change
@@ -1,125 +1,167 @@
import { LambdaColumnMetadata, LambdaExpressionMetadata, LambdaPropertiesMetadata } from "./metadatas";
import { Expression, LambdaExpression } from "./types";
import {
ExpressionMetadata,
LambdaColumnMetadata,
LambdaExpressionMetadata,
LambdaPropertiesMetadata
} from './metadatas';
import { Expression, LambdaExpression } from './types';
import { REGEX_BETWEEN_PARENTHESIS, REGEX_BETWEEN_QUOTES } from './utils';

export class ExpressionUtils {
constructor(private _defaultSeparator: string = '_') {}

private getPropertiesByLambdaExpression<T>(expression: LambdaExpression<T>): LambdaPropertiesMetadata {
const expressionMetadada = this.getExpressionByLambdaExpression(expression);
return {
alias: expressionMetadada.alias,
operator: expressionMetadada.operator,
propertiesLeft: this.getPropertiesByExpressionString(expressionMetadada.expressionLeft, expressionMetadada.alias),
propertiesRight: this.getPropertiesByExpressionString(
expressionMetadada.expressionRight,
expressionMetadada.alias
)
};
}

private getAlias(startExpression: string): string {
// https://stackoverflow.com/a/17779833/2290538
const resultRegex = REGEX_BETWEEN_PARENTHESIS.exec(startExpression);
return resultRegex && resultRegex.length > 1 ? resultRegex[1] : '';
}

private hasAlias(value: string, alias: string | undefined): boolean {
if (!alias) {
return true;
}
return value.indexOf(`${alias}.`) > -1;
}

private hasBetweenQuotes(value: string): boolean {
return REGEX_BETWEEN_QUOTES.test(value);
}

private hasExpression(value: string, alias: string | undefined): boolean {
return !this.hasBetweenQuotes(value) && this.hasAlias(value, alias);
}

public getValueByExpression<T>(instance: T, expression: Expression<T>) {
return expression(instance);
}

public getValueByProperties(instance: any, properties: string[]) {
let result = instance;
properties.forEach(property => {
result = this.getValue(result, property);
});
return result;
}

public getValue(instance: any, property: string) {
if (property.indexOf('.') > -1) {
return this.getValueByProperties(instance, property.split('.'));
}
if (instance) {
return instance[property];
}
return void 0;
}

constructor(private _defaultSeparator: string = "_") {
public getColumnByExpression<T>(expression: Expression<T>, separator: string = this._defaultSeparator): string {
return this.getColumnByProperties(this.getPropertiesByExpression(expression), separator);
}

}
public getColumnByProperties(properties: string[], separator: string = this._defaultSeparator): string {
return properties.join(separator);
}

public getValueByExpression<T>(instance: T, expression: Expression<T>) {
return expression(instance);
}
public getPropertiesByExpression<T>(expression: Expression<T>): string[] {
const lambdaExpression = this.getExpressionMetadata(expression).body;

public getValueByProperties(instance: any, properties: string[]) {
let result = instance;
properties.forEach((property) => {
result = this.getValue(result, property);
});
return result;
}
const properties = this.getPropertiesByExpressionString(lambdaExpression.split(' ')[0]);

public getValue(instance: any, property: string) {
if (property.indexOf(".") > -1) {
return this.getValueByProperties(instance, property.split("."));
}
if (instance) {
return instance[property];
}
return void 0;
}
return properties;
}

public getColumnByExpression<T>(expression: Expression<T>, separator: string = this._defaultSeparator): string {
return this.getColumnByProperties(this.getPropertiesByExpression(expression), separator);
}
private getExpressionMetadata<T>(expression: Expression<T> | LambdaExpression<T>): ExpressionMetadata {
const expressionAsString = expression.toString();

public getColumnByProperties(properties: string[], separator: string = this._defaultSeparator): string {
return properties.join(separator);
if (expressionAsString.search('=>') !== -1) {
return this.getExpressionMetadataES2015(expressionAsString);
} else if (expressionAsString.search('return') !== -1) {
return this.getExpressionMetadataES5(expressionAsString);
} else {
throw Error(`Invalid expression: ${expressionAsString}`);
}
}

public getPropertiesByExpression<T>(expression: Expression<T>): string[] {
let strAfterReturn = expression.toString().split("return")[1].trim();
if (!strAfterReturn) {
strAfterReturn = expression.toString().split("{")[1].trim();
}
const strBeforeSemicon = strAfterReturn.split(" ")[0].split(";")[0];
return this.getPropertiesByExpressionString(strBeforeSemicon);
}
private getExpressionMetadataES5(expressionAsString: string): ExpressionMetadata {
const expressionMetadata = {} as ExpressionMetadata;

public getPropertiesByExpressionString(expression: string, alias: string | undefined = void 0): string[] {
if (this.hasExpression(expression, alias)) {
const propertiesReferences = expression.split(".");
if (propertiesReferences.length && this.hasExpression(expression, alias)) {
propertiesReferences.shift();
} // remove alias
return propertiesReferences;
}
return [expression];
}
const paramsRegEx = REGEX_BETWEEN_PARENTHESIS.exec(expressionAsString);

public getColumnByLambdaExpression<T>(expression: LambdaExpression<T>): LambdaColumnMetadata {
const propertiesMetadada = this.getPropertiesByLambdaExpression(expression);
return {
alias: propertiesMetadada.alias,
columnLeft: this.getColumnByProperties(propertiesMetadada.propertiesLeft),
columnRight: this.getColumnByProperties(propertiesMetadada.propertiesRight),
operator: propertiesMetadada.operator,
};
}
expressionMetadata.params = paramsRegEx && paramsRegEx.length ? paramsRegEx[1] : '';

public getExpressionByLambdaExpression<T>(
expression: LambdaExpression<T>
): LambdaExpressionMetadata {
let splitInitExpression = expression.toString().split("return");
if (!splitInitExpression) {
splitInitExpression = expression.toString().split("{");
}
const strAfterReturn = splitInitExpression[1].trim();
const strExpression = strAfterReturn.split(";")[0].split(" ");

if (strExpression.length !== 3) {
throw new Error(`Lambda expression '${expression.toString()}'
not supported! Use simple expression with '{expressionLeft} {operador} {expressionRight}'`);
}
return {
alias: this.getAlias(splitInitExpression[0]),
expressionLeft: strExpression[0],
expressionRight: strExpression[2],
operator: strExpression[1],
};
}
expressionMetadata.body = expressionAsString.slice(
expressionAsString.indexOf('return') + 7,
expressionAsString.indexOf(';')
);

private getPropertiesByLambdaExpression<T>(expression: LambdaExpression<T>): LambdaPropertiesMetadata {
const expressionMetadada = this.getExpressionByLambdaExpression(expression);
return {
alias: expressionMetadada.alias,
operator: expressionMetadada.operator,
propertiesLeft: this.getPropertiesByExpressionString(expressionMetadada.expressionLeft, expressionMetadada.alias),
propertiesRight: this.getPropertiesByExpressionString(expressionMetadada.expressionRight, expressionMetadada.alias),
};
}
return expressionMetadata;
}

private getAlias(startExpression: string): string {
// https://stackoverflow.com/a/17779833/2290538
const regexBetweenParentheses = /\(([^)]+)\)/;
const resultRegex = regexBetweenParentheses.exec(startExpression);
return resultRegex && resultRegex.length > 1 ? resultRegex[1] : "";
}
private getExpressionMetadataES2015(expressionAsString: string): ExpressionMetadata {
const expressionMetadata = {} as ExpressionMetadata;

private hasAlias(value: string, alias: string | undefined): boolean {
if (!alias) {
return true;
}
return value.indexOf(`${alias}.`) > -1;
}
const params = expressionAsString.slice(0, expressionAsString.indexOf(' =>'));

expressionMetadata.params = REGEX_BETWEEN_PARENTHESIS.test(params) ? this.getAlias(params) : params;

expressionMetadata.body = expressionAsString.slice(expressionAsString.indexOf('=>') + 3, expressionAsString.length);

private hasBetweenQuotes(value: string): boolean {
// let regexBetweenQuotes = /"[^"]*"/;
const regexBetweenQuotes = /^(["'])(.*)\1$/;
return regexBetweenQuotes.test(value);
return expressionMetadata;
}

public getPropertiesByExpressionString(expression: string, alias: string | undefined = void 0): string[] {
if (this.hasExpression(expression, alias)) {
const propertiesReferences = expression.split('.');
if (propertiesReferences.length && this.hasExpression(expression, alias)) {
propertiesReferences.shift();
} // remove alias
return propertiesReferences;
}
return [expression];
}

public getColumnByLambdaExpression<T>(expression: LambdaExpression<T>): LambdaColumnMetadata {
const propertiesMetadada = this.getPropertiesByLambdaExpression(expression);
return {
alias: propertiesMetadada.alias,
columnLeft: this.getColumnByProperties(propertiesMetadada.propertiesLeft),
columnRight: this.getColumnByProperties(propertiesMetadada.propertiesRight),
operator: propertiesMetadada.operator
};
}

public getExpressionByLambdaExpression<T>(expression: LambdaExpression<T>): LambdaExpressionMetadata {
const lambdaExpressionMetadata = {} as LambdaExpressionMetadata;

private hasExpression(value: string, alias: string | undefined): boolean {
return !this.hasBetweenQuotes(value) && this.hasAlias(value, alias);
const { params, body } = this.getExpressionMetadata(expression);

lambdaExpressionMetadata.alias = params;

const strExpression = body.split(' ');

if (strExpression.length !== 3) {
throw new Error(`Lambda expression '${expression.toString()}'
not supported! Use simple expression with '{expressionLeft} {operador} {expressionRight}'`);
}

[
lambdaExpressionMetadata.expressionLeft,
lambdaExpressionMetadata.operator,
lambdaExpressionMetadata.expressionRight
] = strExpression;

return lambdaExpressionMetadata;
}
}
9 changes: 7 additions & 2 deletions src/metadatas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ export interface LambdaColumnMetadata extends LambdaExpressionMetadataBase {
columnRight: string;
}

export interface LambdaExpressionMetadataBase{
export interface LambdaExpressionMetadataBase {
operator: string;
alias: string;
}
}

export interface ExpressionMetadata {
params: string;
body: string;
}
Loading