Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(reflection): added missing parsing options #180

Merged
merged 6 commits into from
Feb 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/jitar-reflection/src/models/ReflectionAlias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,9 @@ export default class ReflectionAlias
get name(): string { return this.#name; }

get as(): string { return this.#as; }

toString(): string
{
return `${this.#name} as ${this.#as}`;
}
}
5 changes: 5 additions & 0 deletions packages/jitar-reflection/src/models/ReflectionClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,9 @@ export default class ReflectionClass extends ReflectionMember

return funktion !== undefined && funktion.isPublic;
}

toString(): string
{
return `class ${this.name}${this.#parentName !== undefined ? ` extends ${this.#parentName}` : ''} { ${this.#scope.toString()} }`;
}
}
5 changes: 5 additions & 0 deletions packages/jitar-reflection/src/models/ReflectionExport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@ export default class ReflectionExport extends ReflectionMember
get members() { return this.#members; }

get from() { return this.#from; }

toString(): string
{
return `export { ${this.#members.join(', ')} }${this.#from ? ` from '${this.#from}'` : ''}`;
}
}
5 changes: 5 additions & 0 deletions packages/jitar-reflection/src/models/ReflectionField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ export default class ReflectionField extends ReflectionMember
}

get value() { return this.#value; }

toString(): string
{
return `${this.name}${this.value ? ' = ' + this.value.toString() : ''}`;
}
}
7 changes: 7 additions & 0 deletions packages/jitar-reflection/src/models/ReflectionFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,11 @@ export default class ReflectionFunction extends ReflectionMember
get body() { return this.#body; }

get isAsync() { return this.#isAsync; }

toString(): string
{
const parameters = this.parameters.map((parameter) => parameter.toString());

return `${this.isAsync ? 'async ' : ''}${this.name}(${parameters.join(', ')}) { ${this.body} }`;
}
}
7 changes: 6 additions & 1 deletion packages/jitar-reflection/src/models/ReflectionGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@ import ReflectionFunction from './ReflectionFunction.js';

export default class ReflectionGenerator extends ReflectionFunction
{

toString(): string
{
const parameters = this.parameters.map((parameter) => parameter.toString());

return `${this.isAsync ? 'async ' : ''}${this.name}*(${parameters.join(', ')}) { ${this.body} }`;
}
}
5 changes: 4 additions & 1 deletion packages/jitar-reflection/src/models/ReflectionGetter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@ import ReflectionFunction from './ReflectionFunction.js';

export default class ReflectionGetter extends ReflectionFunction
{

toString(): string
{
return `get ${super.toString()}`;
}
}
5 changes: 5 additions & 0 deletions packages/jitar-reflection/src/models/ReflectionImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@ export default class ReflectionImport extends ReflectionMember
get members() { return this.#members; }

get from() { return this.#from; }

toString(): string
{
return `import { ${this.#members.map(member => member.toString()).join(', ')} } from '${this.#from}';`;
}
}
5 changes: 5 additions & 0 deletions packages/jitar-reflection/src/models/ReflectionScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,9 @@ export default class ReflectionScope
{
return this.getClass(name) !== undefined;
}

toString(): string
{
return this.#members.map(member => member.toString()).join('\n');
}
}
5 changes: 4 additions & 1 deletion packages/jitar-reflection/src/models/ReflectionSetter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@ import ReflectionFunction from './ReflectionFunction.js';

export default class ReflectionSetter extends ReflectionFunction
{

toString(): string
{
return `set ${super.toString()}`;
}
}
5 changes: 5 additions & 0 deletions packages/jitar-reflection/src/models/ReflectionValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ export default class ReflectionValue
}

get definition() { return this.#definition; }

toString(): string
{
return this.#definition;
}
}
3 changes: 2 additions & 1 deletion packages/jitar-reflection/src/parser/Lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export default class Lexer
const isOther = isEmpty(char)
|| isWhitespace(char)
|| isOperator(char)
|| isLiteral(char)
|| isDivider(char)
|| isGroup(char)
|| isScope(char)
Expand Down Expand Up @@ -176,7 +177,7 @@ export default class Lexer
{
const char = charList.current;

if (isLiteral(char) && char === identifier)
if (char === identifier && charList.previous !== '\\')
{
break;
}
Expand Down
35 changes: 28 additions & 7 deletions packages/jitar-reflection/src/parser/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Lexer from './Lexer.js';
import Token from './Token.js';
import TokenList from './TokenList.js';

import { Divider } from './definitions/Divider.js';
import { Divider, isDivider } from './definitions/Divider.js';
import { Group } from './definitions/Group.js';
import { Keyword, isDeclaration, isKeyword } from './definitions/Keyword.js';
import { List } from './definitions/List.js';
Expand Down Expand Up @@ -212,8 +212,13 @@ export default class Parser
// Regular expression or logical not
return this.#parseExpression(tokenList);
}
else if (token.hasValue(Divider.TERMINATOR) || token.hasValue(Divider.SEPARATOR))
else if (isDivider(token.value))
{
// Use cases:
// - Terminator as end of statement
// - Separator as multi declaration
// - Scope as ternary else expression

tokenList.step(); // Read away the divider

return undefined;
Expand Down Expand Up @@ -440,11 +445,27 @@ export default class Parser
#parseDeclaration(tokenList: TokenList, isStatic: boolean): ReflectionMember
{
let token = tokenList.current;
let name = ANONYMOUS_IDENTIFIER;
let isPrivate = false;

const isPrivate = token.value.startsWith(PRIVATE_INDICATOR);
const name = isPrivate ? token.value.substring(1) : token.value;

token = tokenList.step(); // Read away the field name
if (token.hasValue(List.OPEN))
{
// Array destructuring
name = this.#parseBlock(tokenList, List.OPEN, List.CLOSE);
token = tokenList.current;
}
else if (token.hasValue(Scope.OPEN))
{
// Object destructuring
name = this.#parseBlock(tokenList, Scope.OPEN, Scope.CLOSE);
token = tokenList.current;
}
else
{
isPrivate = token.value.startsWith(PRIVATE_INDICATOR);
name = isPrivate ? token.value.substring(1) : token.value;
token = tokenList.step(); // Read away the field name
}

let value = undefined;

Expand Down Expand Up @@ -828,7 +849,7 @@ export default class Parser

#atEndOfStatement(token: Token): boolean
{
return [Divider.SEPARATOR, Divider.TERMINATOR].includes(token.value)
return [Divider.TERMINATOR, Divider.SEPARATOR].includes(token.value)
|| [List.CLOSE, Group.CLOSE, Scope.CLOSE].includes(token.value)
|| isKeyword(token.value);
}
Expand Down
10 changes: 6 additions & 4 deletions packages/jitar-reflection/test/_fixtures/parser/Lexer.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
const CODE =
{
OPERATORS: `=====!=/=/!=`,
STATEMENT: `const identifier = (12 >= 3) ? { 'foo' } : [ "bar" ];`,
WHITESPACE_EXCLUDED: `const identifier="value";`,
WHITESPACE_INCLUDED: `const identifier\n=\t"value" ;`,
LITERALS: '`foo\\`ter`"bar\\"becue"\'baz\'',
KEYWORDS_IDENTIFIERS: 'class Foo function bar',
WHITESPACE: `const identifier\n=\t"value" ;`,
COMMENT_LINE: `const // This is a comment\nidentifier`,
COMMENT_BLOCK: `const /* This is a comment */ identifier`
COMMENT_BLOCK: `const /* This is a comment */ identifier`,
STATEMENT: `const identifier = (12 >= 3) ? { 'foo' } : [ "bar" ];`,
MINIFIED: 'return`foo`;identifier1=identifier2'
}

export { CODE };
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ const FIELDS =
ARRAY: "const array = [ 'value1', 'value2' ];",
OBJECT: "const object = { key1: 'value1', key2: 'value2' };",
REGEX: "const regex = /regex/g;",
DESTRUCTURING_ARRAY: "const [value1, value2] = array;",
DESTRUCTURING_OBJECT: "const {key1, key2} = object;",
}

const FUNCTIONS =
Expand Down Expand Up @@ -123,7 +125,7 @@ const CLASSES =
async *generator2() { yield 1; }

static async *generator3() { yield 1; }
}`,
}`
}

const MODULES =
Expand Down
82 changes: 59 additions & 23 deletions packages/jitar-reflection/test/parser/Lexer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,42 @@ describe('parser/Lexer', () =>
expect(tokens.get(5).value).toBe(Operator.NOT_EQUAL);
});

it('should separate keywords from literals', () =>
{
const tokens = lexer.tokenize(CODE.LITERALS);
expect(tokens.size).toBe(3);

expect(tokens.get(0).type).toBe(TokenType.LITERAL);
expect(tokens.get(0).value).toBe('`foo\\`ter`');

expect(tokens.get(1).type).toBe(TokenType.LITERAL);
expect(tokens.get(1).value).toBe('"bar\\"becue"');

expect(tokens.get(2).type).toBe(TokenType.LITERAL);
expect(tokens.get(2).value).toBe("'baz'");
});

it('should separate keywords from identifiers', () =>
{
const tokens = lexer.tokenize(CODE.KEYWORDS_IDENTIFIERS);
expect(tokens.size).toBe(4);

expect(tokens.get(0).type).toBe(TokenType.KEYWORD);
expect(tokens.get(0).value).toBe('class');

expect(tokens.get(1).type).toBe(TokenType.IDENTIFIER);
expect(tokens.get(1).value).toBe('Foo');

expect(tokens.get(2).type).toBe(TokenType.KEYWORD);
expect(tokens.get(2).value).toBe('function');

expect(tokens.get(3).type).toBe(TokenType.IDENTIFIER);
expect(tokens.get(3).value).toBe('bar');
});

it('should include whitespace when requested', () =>
{
const tokens = lexer.tokenize(CODE.WHITESPACE_INCLUDED, false);
const tokens = lexer.tokenize(CODE.WHITESPACE, false);
expect(tokens.size).toBe(9);

expect(tokens.get(0).type).toBe(TokenType.KEYWORD);
Expand Down Expand Up @@ -77,28 +110,7 @@ describe('parser/Lexer', () =>

it('should omit whitespace when requested', () =>
{
const tokens = lexer.tokenize(CODE.WHITESPACE_INCLUDED, true);
expect(tokens.size).toBe(5);

expect(tokens.get(0).type).toBe(TokenType.KEYWORD);
expect(tokens.get(0).value).toBe('const');

expect(tokens.get(1).type).toBe(TokenType.IDENTIFIER);
expect(tokens.get(1).value).toBe('identifier');

expect(tokens.get(2).type).toBe(TokenType.OPERATOR);
expect(tokens.get(2).value).toBe(Operator.ASSIGN);

expect(tokens.get(3).type).toBe(TokenType.LITERAL);
expect(tokens.get(3).value).toBe('"value"');

expect(tokens.get(4).type).toBe(TokenType.DIVIDER);
expect(tokens.get(4).value).toBe(Divider.TERMINATOR);
});

it('should tokenize when whitespace is excluded', () =>
{
const tokens = lexer.tokenize(CODE.WHITESPACE_EXCLUDED, true);
const tokens = lexer.tokenize(CODE.WHITESPACE, true);
expect(tokens.size).toBe(5);

expect(tokens.get(0).type).toBe(TokenType.KEYWORD);
Expand Down Expand Up @@ -227,5 +239,29 @@ describe('parser/Lexer', () =>
expect(tokens.get(16).type).toBe(TokenType.DIVIDER);
expect(tokens.get(16).value).toBe(Divider.TERMINATOR);
});

it('should tokenize minified code', () =>
{
const tokens = lexer.tokenize(CODE.MINIFIED);
expect(tokens.size).toBe(6);

expect(tokens.get(0).type).toBe(TokenType.IDENTIFIER);
expect(tokens.get(0).value).toBe('return');

expect(tokens.get(1).type).toBe(TokenType.LITERAL);
expect(tokens.get(1).value).toBe('`foo`');

expect(tokens.get(2).type).toBe(TokenType.DIVIDER);
expect(tokens.get(2).value).toBe(Divider.TERMINATOR);

expect(tokens.get(3).type).toBe(TokenType.IDENTIFIER);
expect(tokens.get(3).value).toBe('identifier1');

expect(tokens.get(4).type).toBe(TokenType.OPERATOR);
expect(tokens.get(4).value).toBe(Operator.ASSIGN);

expect(tokens.get(5).type).toBe(TokenType.IDENTIFIER);
expect(tokens.get(5).value).toBe('identifier2');
});
});
});
24 changes: 21 additions & 3 deletions packages/jitar-reflection/test/parser/Parser.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@

import { describe, expect, it } from 'vitest';

import ReflectionExpression from '../../src/models/ReflectionExpression';
import ReflectionArray from '../../src/models/ReflectionArray';
import ReflectionExpression from '../../src/models/ReflectionExpression';
import ReflectionField from '../../src/models/ReflectionField';
import ReflectionGenerator from '../../src/models/ReflectionGenerator';
import ReflectionObject from '../../src/models/ReflectionObject';
import Parser from '../../src/parser/Parser';

import { VALUES, IMPORTS, EXPORTS, FIELDS, FUNCTIONS, CLASSES, MODULES } from '../_fixtures/parser/Parser.fixture';
import ReflectionField from '../../src/models/ReflectionField';
import ReflectionGenerator from '../../src/models/ReflectionGenerator';

const parser = new Parser();

Expand Down Expand Up @@ -359,6 +359,24 @@ describe('parser/Parser', () =>
expect(field.value).toBeInstanceOf(ReflectionExpression);
expect(field.value?.definition).toBe("/ regex / g");
});

it('should parse a field that is destructuring an array', () =>
{
const field = parser.parseField(FIELDS.DESTRUCTURING_ARRAY);

expect(field.name).toBe('[ value1 , value2 ]');
expect(field.value).toBeInstanceOf(ReflectionExpression);
expect(field.value?.definition).toBe("array");
});

it('should parse a field that is destructuring an object', () =>
{
const field = parser.parseField(FIELDS.DESTRUCTURING_OBJECT);

expect(field.name).toBe('{ key1 , key2 }');
expect(field.value).toBeInstanceOf(ReflectionExpression);
expect(field.value?.definition).toBe("object");
});
});

describe('.parseFunction(code)', () =>
Expand Down