Skip to content

Commit

Permalink
feat: add support for numeric types and lists.
Browse files Browse the repository at this point in the history
  • Loading branch information
gumptious committed Nov 14, 2019
1 parent 7a7a804 commit a443405
Show file tree
Hide file tree
Showing 10 changed files with 382 additions and 209 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class ViewController: UIViewController {

// Subscribe to component changes.
diez.attach(self, subscriber: {(component: MyStateBag) in
self.label.text = component.copy
self.label.text = "\(component.copy). \(component.numbers[10])"
component.textStyle.setTextStyle(forLabel: self.label)
self.label.sizeToFit()
self.label.textAlignment = .center
Expand Down
4 changes: 3 additions & 1 deletion examples/playground/src/MyStateBag.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Color, File, FontRegistry, Haiku, Image, IOSFonts, Lottie, Palette, SVG, TextStyle} from '@diez/designsystem';
import {Component, expression, method, property, shared} from '@diez/engine';
import {Component, expression, Integer, method, property, shared} from '@diez/engine';
import {easeInOutExpo} from 'just-curves';

class MyPalette extends Component<Palette> {
Expand All @@ -16,6 +16,8 @@ export class MyStateBag extends Component {

@property copy = `Hello ${this.name}`;

@property numbers: Integer[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

@property image = new Image({
file: new File({
// Try changing this to diez.jpg!
Expand Down
72 changes: 63 additions & 9 deletions packages/compiler/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {ConcreteComponentType} from '@diez/engine';
import {Express, RequestHandler} from 'express';
import {Type} from 'ts-morph';
import {ClassDeclaration, Project, Type, TypeChecker} from 'ts-morph';
import {Configuration} from 'webpack';

/**
Expand All @@ -19,6 +19,16 @@ export type WebpackConfigModifier = (config: Configuration) => void;
*/
export type HotServerModifier = (app: Express, projectRoot: string) => void;

/**
* Provides an arbitrarily nested array type, i.e. `T[] | T[][] | T[][] | …`.
*/
export interface NestedArray<T> extends Array<T | NestedArray<T>> {}

/**
* Provides an arbitrarily nested array type with support for no nesting, i.e. `T | T[] | T[][] | …`.
*/
export type MaybeNestedArray<T> = T | NestedArray<T>;

/**
* A component compiler property descriptor.
*/
Expand All @@ -32,7 +42,11 @@ export interface TargetProperty {
*/
isComponent: boolean;
/**
* The unique type name, used only for component properties.
* The depth of the target property. This allows arbitrary-order list types.
*/
depth: number;
/**
* The unique type name.
*/
type?: string;
}
Expand Down Expand Up @@ -82,13 +96,7 @@ export type NamedComponentMap = Map<string, TargetComponent>;
/**
* Compiler target handlers perform the actual work of compilation.
*/
export type CompilerTargetHandler = (
projectRoot: string,
destinationPath: string,
localComponentNames: string[],
namedComponentMap: NamedComponentMap,
devMode: boolean,
) => void;
export type CompilerTargetHandler = (program: CompilerProgram) => void;

/**
* A generic interface for a compiler target.
Expand All @@ -104,3 +112,49 @@ export interface CompilerTargetProvider {
export interface ComponentModule {
[key: string]: ConcreteComponentType;
}

/**
* A complete compiler program.
*/
export interface CompilerProgram {
/**
* A typechecker capable of resolving any known types.
*/
checker: TypeChecker;
/**
* The source file providing the entry point for our compiler program.
*/
project: Project;
/**
* The component declaration, which we can use to determine component-ness using the typechecker.
*/
componentDeclaration: ClassDeclaration;
/**
* A collection of reserved types, used to resolve type ambiguities in key places.
*/
types: {
int: Type;
float: Type;
};
/**
* A map of (unique!) component names to target component specifications. This is derived recursively
* and includes both prefabs from external modules and local components.
*/
targetComponents: NamedComponentMap;
/**
* The names of local components encountered during compiler execution.
*/
localComponentNames: string[];
/**
* The root of the project whose local components we should compile.
*/
projectRoot: string;
/**
* The destination path of the project whose local components we should compile.
*/
destinationPath: string;
/**
* Whether we are running the compiler in dev mode or not.
*/
devMode: boolean;
}
45 changes: 11 additions & 34 deletions packages/compiler/src/commands/compile.action.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
/* tslint:disable:max-line-length */
import {fatalError, info} from '@diez/cli';
import {join, resolve} from 'path';
import {ClassDeclaration} from 'ts-morph';
import {NamedComponentMap} from '../api';
import {getTargets, getValidProject, printWarnings, processType, runPackageScript, shouldUseYarn} from '../utils';
import {getTargets, getValidProgram, printWarnings, processType} from '../utils';

interface CompileOptions {
output: string;
Expand All @@ -23,49 +21,28 @@ export const compileAction = async ({output, target: targetIn, dev}: CompileOpti

const targetHandler = targets.get(target)!;

const projectRoot = global.process.cwd();
const targetComponents: NamedComponentMap = new Map();
const program = await getValidProgram(global.process.cwd(), resolve(output), dev);

info(`Validating project structure at ${projectRoot}...`);
const project = getValidProject(projectRoot);
const useYarn = await shouldUseYarn();
info('Compiling TypeScript sources...');
const compilationSucceeded = await runPackageScript('tsc', useYarn, projectRoot);
if (!compilationSucceeded) {
fatalError('Unable to compile project.');
}

// Create a stub type file for typing the class
const stubTypeFile = project.createSourceFile(
'src/__stub.ts',
"import {Component} from '@diez/engine';",
);

const sourceFile = project.getSourceFileOrThrow(join('src', 'index.ts'));

const checker = project.getTypeChecker();
const engineImports = stubTypeFile.getImportDeclarationOrThrow('@diez/engine').getNamedImports();
const componentDeclaration = checker.getTypeAtLocation(engineImports[0]).getSymbolOrThrow().getValueDeclarationOrThrow() as ClassDeclaration;
info(`Unwrapping component types from ${resolve(projectRoot, 'src', 'index.ts')}...`);
const foundComponents: string[] = [];
const sourceFile = program.project.getSourceFileOrThrow(join('src', 'index.ts'));
info(`Unwrapping component types from ${resolve(program.projectRoot, 'src', 'index.ts')}...`);
for (const exportDeclaration of sourceFile.getExportDeclarations()) {
for (const exportSpecifier of exportDeclaration.getNamedExports()) {
const moduleName = exportDeclaration.getModuleSpecifierValue()!;
if (!moduleName.startsWith('.')) {
continue;
}

const type = checker.getTypeAtLocation(exportSpecifier);
if (processType(checker, type, componentDeclaration, targetComponents)) {
foundComponents.push(exportSpecifier.getName());
const type = program.checker.getTypeAtLocation(exportSpecifier);
if (processType(type, program)) {
program.localComponentNames.push(exportSpecifier.getName());
}
}
}

if (!foundComponents.length) {
fatalError('No components found!');
if (!program.localComponentNames.length) {
fatalError('No local components found!');
}

printWarnings(targetComponents);
await targetHandler(projectRoot, resolve(output), foundComponents, targetComponents, dev);
printWarnings(program.targetComponents);
await targetHandler(program);
};

0 comments on commit a443405

Please sign in to comment.