Skip to content
This repository has been archived by the owner on Oct 23, 2023. It is now read-only.

Commit

Permalink
feat(store): support for more property types
Browse files Browse the repository at this point in the history
  • Loading branch information
TheReincarnator committed Dec 7, 2017
1 parent 8619312 commit 9a96741
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 90 deletions.
3 changes: 2 additions & 1 deletion src/store/page/page_element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export class PageElement {
this.pattern = pattern;
} else {
console.warn(`Ignoring unknown pattern ${this.patternPath}`);
return;
}

if (json.properties) {
Expand Down Expand Up @@ -66,7 +67,7 @@ export class PageElement {
// tslint:disable-next-line:no-any
public setPropertyValue(id: string, value: any): void {
if (!this.pattern) {
throw new Error('This element has no pattern');
throw new Error('This element has no valid pattern');
}

const property: Property | undefined = this.pattern.getProperty(id);
Expand Down
8 changes: 2 additions & 6 deletions src/store/pattern/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { Property } from './property';
import { TypeScriptParser } from './parser/typescript_parser';

export class Pattern {
private static parsers: PatternParser[];

/**
* The name of the pattern folder.
*/
Expand Down Expand Up @@ -63,13 +61,11 @@ export class Pattern {
}

public reload(): void {
if (!Pattern.parsers) {
Pattern.parsers = [new TypeScriptParser()];
}
const parsers: PatternParser[] = [new TypeScriptParser()];

this.valid = false;
this.properties.clear();
Pattern.parsers.some(parser => {
parsers.some(parser => {
const result: Property[] | undefined = parser.parse(this);
if (result) {
result.forEach(property => {
Expand Down
179 changes: 119 additions & 60 deletions src/store/pattern/parser/typescript_parser.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,60 @@
// TODO: import { StringArrayProperty } from '../property/string_array_property';
import { BooleanProperty } from '../property/boolean_property';
// TODO: import { PatternProperty } from '../property/pattern_property';
import { EnumProperty, Option } from '../property/enum_property';
import * as FileUtils from 'fs';
import { NumberArrayProperty } from '../property/number_array_property';
import { NumberProperty } from '../property/number_property';
// TODO: import { NumberArrayProperty } from '../property/number_array_property';
import { ObjectProperty } from '../property/object_property';
import * as PathUtils from 'path';
import { Property } from '../property';
import { StringArrayProperty } from '../property/string_array_property';
import { StringProperty } from '../property/string_property';
// TODO: import { PatternProperty } from '../property/pattern_property';
// TODO: import { EnumProperty } from '../property/enum_property';
import * as ts from 'typescript';
import { PatternParser } from '.';
import { Pattern } from '..';

export class TypeScriptParser extends PatternParser {
protected enums: { [name: string]: ts.EnumDeclaration } = {};
protected propsDeclaration?: ts.InterfaceDeclaration;
protected properties: Property[] = [];
protected sourceFile?: ts.SourceFile;
protected typeName?: string;

protected analyzeDeclarations(): void {
this.enums = {};
this.propsDeclaration = undefined;
this.typeName = undefined;

// Phase one: Find type name
(this.sourceFile as ts.SourceFile).forEachChild(node => {
if (ts.isExportAssignment(node)) {
const assignment: ts.ExportAssignment = node;
this.typeName = assignment.expression.getText();
}
});

// Phase two: find props interface, enums, and pattern props imports
(this.sourceFile as ts.SourceFile).forEachChild(node => {
if (ts.isInterfaceDeclaration(node)) {
const declaration: ts.InterfaceDeclaration = node;
if (declaration.name.getText() === this.getPropsTypeName()) {
this.propsDeclaration = declaration;
}
} else if (ts.isEnumDeclaration(node)) {
const declaration: ts.EnumDeclaration = node;
this.enums[declaration.name.getText()] = declaration;
}
});
}

protected getPropsTypeName(): string {
return this.typeName + 'Props';
}

public parse(pattern: Pattern): Property[] | undefined {
this.sourceFile = undefined;
this.properties = [];

const folderPath: string = pattern.getAbsolutePath();
const declarationPath = PathUtils.join(folderPath, 'index.d.ts');
const implementationPath = PathUtils.join(folderPath, 'index.js');
Expand All @@ -29,88 +69,107 @@ export class TypeScriptParser extends PatternParser {
return undefined;
}

const sourceFile: ts.SourceFile = ts.createSourceFile(
this.sourceFile = ts.createSourceFile(
declarationPath,
FileUtils.readFileSync(declarationPath).toString(),
ts.ScriptTarget.ES2016,
true
);

let typeName: string | undefined;
sourceFile.forEachChild(node => {
if (ts.isExportAssignment(node)) {
const assignment: ts.ExportAssignment = node;
typeName = assignment.expression.getText();
}
});

if (!typeName) {
this.analyzeDeclarations();
if (!this.typeName) {
console.warn(`Invalid pattern "${declarationPath}": No type name found`);
return undefined;
}

let declaration: ts.InterfaceDeclaration | undefined;
sourceFile.forEachChild(node => {
if (ts.isInterfaceDeclaration(node)) {
const candidate: ts.InterfaceDeclaration = node;
if (candidate.name.escapedText === (typeName as string) + 'Props') {
declaration = candidate;
}
}
});

if (!declaration) {
if (!this.propsDeclaration) {
console.warn(`Invalid pattern "${declarationPath}": No props interface found`);
return undefined;
}

const properties: Property[] = [];
declaration.forEachChild((node: ts.Node) => {
this.propsDeclaration.forEachChild((node: ts.Node) => {
if (ts.isPropertySignature(node)) {
const signature: ts.PropertySignature = node;
const id: string = signature.name.getText();
this.processProperty(node);
}
});

// TODO: Replace by actual name
const name: string = signature.name.getText();
return this.properties;
}

const required: boolean = signature.questionToken === undefined;
protected processProperty(signature: ts.PropertySignature): void {
const required: boolean = signature.questionToken === undefined;

const typeNode: ts.TypeNode | undefined = signature.type;
if (!typeNode) {
return;
}
const id: string = signature.name.getText();
// TODO: Replace by actual name
const name: string = id;

const typeNode: ts.TypeNode | undefined = signature.type;
if (!typeNode) {
return;
}

let property: Property;
switch (typeNode.kind) {
let property: Property | undefined;
switch (typeNode.kind) {
case ts.SyntaxKind.StringKeyword:
property = new StringProperty(id, name, required);
break;

case ts.SyntaxKind.NumberKeyword:
property = new NumberProperty(id, name, required);
break;

case ts.SyntaxKind.BooleanKeyword:
property = new BooleanProperty(id, name, required);
break;

case ts.SyntaxKind.ArrayType:
switch ((typeNode as ts.ArrayTypeNode).elementType.kind) {
case ts.SyntaxKind.StringKeyword:
// TODO: if (isArray) {
// type = PropertyType.STRING_ARRAY;
// } else {
property = new StringProperty(id, name, required);
// }
property = new StringArrayProperty(id, name, required);
break;

case ts.SyntaxKind.NumberKeyword:
// TODO: if (isArray) {
// type = PropertyType.NUMBER_ARRAY;
// } else {
property = new NumberProperty(id, name, required);
// }
break;
property = new NumberArrayProperty(id, name, required);
}
break;

case ts.SyntaxKind.BooleanKeyword:
property = new BooleanProperty(id, name, required);
break;
case ts.SyntaxKind.TypeReference:
const referenceNode = typeNode as ts.TypeReferenceNode;
property = this.processTypeProperty(id, name, required, referenceNode);
}

// TODO: Pattern type
if (!property) {
property = new ObjectProperty(id, name, required);
}

default:
property = new ObjectProperty(id, name, required);
}
this.properties.push(property);
}

properties.push(property);
}
protected processTypeProperty(
id: string,
name: string,
required: boolean,
referenceNode: ts.TypeReferenceNode
): Property | undefined {
if (!referenceNode.typeName) {
return undefined;
}

// TODO: Pattern type

const enumTypeName: string = referenceNode.typeName.getText();
const enumDeclaration: ts.EnumDeclaration | undefined = this.enums[enumTypeName];
if (!enumDeclaration) {
return undefined;
}

const options: Option[] = [];
enumDeclaration.members.forEach(enumMember => {
const enumMemberId = enumMember.name.getText();
// TODO: Replace by actual name
const enumMemberName = enumMemberId;
options.push(new Option(enumMemberId, enumMemberName));
});
return properties;

return new EnumProperty(id, name, required, options);
}
}
23 changes: 22 additions & 1 deletion src/store/pattern/property/enum_property.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Option } from './option';
import { Property } from '.';

export class EnumProperty extends Property {
Expand Down Expand Up @@ -30,3 +29,25 @@ export class EnumProperty extends Property {
return `EnumProperty(id="${this.getId()}", required="${this.isRequired()}")`;
}
}

export class Option {
private id: string;
private name: string;

public constructor(id: string, name: string) {
this.id = id;
this.name = name;
}

public getId(): string {
return this.id;
}

public getName(): string {
return this.name;
}

public toString(): string {
return `Option(id="${this.id}", name="${this.name}")`;
}
}
21 changes: 0 additions & 21 deletions src/store/pattern/property/option.ts

This file was deleted.

2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"forceConsistentCasingInFileNames": true,
"importHelpers": false,
"inlineSourceMap": true,
"inlineSources": false,
"inlineSources": true,
"isolatedModules": false,
"jsx": "react",
"module": "commonjs",
Expand Down

0 comments on commit 9a96741

Please sign in to comment.