Skip to content

Commit

Permalink
WIP: v1
Browse files Browse the repository at this point in the history
  • Loading branch information
jahudka committed Nov 13, 2024
1 parent acd253b commit faf3aba
Show file tree
Hide file tree
Showing 82 changed files with 5,114 additions and 474 deletions.
23 changes: 13 additions & 10 deletions core/cli/dicc.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
containers:
src/bootstrap.ts:
className: DiccContainer
src/bootstrap2.ts:
className: ThiccContainer
lazyImports: false
resources:
src/argv.ts: ~
src/autowiring.ts: ~
src/checker.ts: ~
src/configLoader.ts: ~
src/definitions.ts: ~
src/definitionScanner.ts: ~
src/sourceFiles.ts: ~
src/typeHelper.ts: ~
# src/argv.ts: ~
# src/autowiring.ts: ~
# src/checker.ts: ~
# src/configLoader.ts: ~
# src/definitions.ts: ~
src/definitions2.ts: ~
src/decco.ts: ~
# src/definitionScanner.ts: ~
# src/sourceFiles.ts: ~
# src/typeHelper.ts: ~
14 changes: 11 additions & 3 deletions core/cli/src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@ import * as checker0 from './checker';
import * as configLoader0 from './configLoader';
import * as definitionScanner0 from './definitionScanner';
import * as definitions0 from './definitions';
import * as iffy0 from './iffy';
import * as sourceFiles0 from './sourceFiles';
import * as typeHelper0 from './typeHelper';

interface Services {
interface PublicServices {
'debug.logger': ServiceType<typeof definitions0.debug.logger>;
'dicc': Promise<ServiceType<typeof definitions0.dicc>>;
}

interface DynamicServices {
'#Iffy.0': iffy0.Iffy;
}

interface AnonymousServices {
'#Argv.0': argv0.Argv;
'#AutowiringFactory.0': autowiring0.AutowiringFactory;
'#Checker.0': Promise<checker0.Checker>;
Expand All @@ -26,9 +34,9 @@ interface Services {
'#TypeHelper.0': Promise<typeHelper0.TypeHelper>;
}

export class DiccContainer extends Container<Services>{
export class DiccContainer extends Container<PublicServices, DynamicServices, AnonymousServices>{
constructor() {
super({}, {
super({
'debug.logger': {
...definitions0.debug.logger,
aliases: ['#Logger.0'],
Expand Down
65 changes: 37 additions & 28 deletions core/cli/src/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,48 @@ export class Checker {

checkAutoFactories(builder: ContainerBuilder): void {
for (const definition of builder.getDefinitions()) {
if (!definition.factory && definition.type.isInterface()) {
const [signature, method] = this.helper.resolveAutoFactorySignature(definition.type);
if (definition.factory) {
definition.id === '#HelloFactory.0' && this.logger.warning(`${definition.id} has factory`);
continue;
}

if (!signature) {
continue;
}
const [signature, method] = this.helper.resolveAutoFactorySignature(definition.type);

const [serviceType, async] = this.helper.unwrapAsyncType(signature.getReturnType());
const [serviceDef, ...rest] = builder.getByType(serviceType);
if (!signature) {
definition.id === '#HelloFactory.0' && this.logger.warning(`${definition.id} has no signature`);
continue;
}

if (!serviceDef) {
continue;
} else if (rest.length) {
throw new DefinitionError(`Multiple services satisfy return type of auto factory '${definition.id}'`);
} else if (!serviceDef.factory) {
throw new DefinitionError(`Cannot auto-implement factory '${definition.id}': unable to resolve target service factory`);
}
const [serviceType, async] = this.helper.unwrapAsyncType(signature.getReturnType());
const [serviceDef, ...rest] = builder.getByType(serviceType);

if (!serviceDef) {
definition.id === '#HelloFactory.0' && this.logger.warning(`${definition.id} has no target service`);
continue;
} else if (rest.length) {
throw new DefinitionError(`Multiple services satisfy return type of auto factory '${definition.id}'`);
} else if (!serviceDef.factory) {
throw new DefinitionError(`Cannot auto-implement factory '${definition.id}': unable to resolve target service factory`);
}

this.logger.debug(`Promoting '${definition.id}' to auto-factory of '${serviceDef.id}'`);

const manualArgs = signature.getParameters().map((p) => p.getName());

const manualArgs = signature.getParameters().map((p) => p.getName());

definition.creates = {
method,
manualArgs,
async,
source: serviceDef.source,
path: serviceDef.path,
type: serviceDef.type,
object: serviceDef.object,
explicit: serviceDef.explicit,
factory: serviceDef.factory,
args: serviceDef.args,
};
definition.creates = {
method,
manualArgs,
async,
source: serviceDef.source,
path: serviceDef.path,
type: serviceDef.type,
object: serviceDef.object,
explicit: serviceDef.explicit,
factory: serviceDef.factory,
args: serviceDef.args,
};

if (method !== 'get') {
builder.unregister(serviceDef.id);
}
}
Expand Down
62 changes: 55 additions & 7 deletions core/cli/src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,11 @@ export class Compiler {
// else definition is an object with a factory function with zero arguments and no decorators,
// so it is already included in the compiled definition courtesy of object spread
} else if (creates) {
this.compileAutoFactory(writer, src, path, creates, sources);
if (creates.method === 'get') {
this.compileAutoClassAccessor(writer, src, path, creates, object);
} else {
this.compileAutoFactory(writer, src, path, creates, sources, type, object);
}
} else {
writer.writeLine(`factory: undefined,`);
}
Expand Down Expand Up @@ -317,24 +321,49 @@ export class Compiler {
writer.write('},\n');
}

private compileAutoClassAccessor(
writer: CodeBlockWriter,
source: string,
path: string,
creates: AutoFactoryTarget,
object?: boolean,
): void {
writer.writeLine(`factory: (di) => new class extends ${join('.', source, path, object && 'factory')} {`);
writer.indent(() => {
writer.conditionalWrite(creates.async, 'async ');
writer.write('get() {');
writer.indent(() => writer.write(`return di.get('${this.builder.getTypeId(creates.type)}');`));
writer.write('}');
});
writer.write('},\n');
}

private compileAutoFactory(
writer: CodeBlockWriter,
source: string,
path: string,
creates: AutoFactoryTarget,
sources: Map<SourceFile, string>,
type: Type,
object?: boolean,
): void {
const args = this.compileArguments(creates.factory.args, {
...(creates.args ? this.compileOverrides(writer, source, path, creates.args) : {}),
...Object.fromEntries(creates.manualArgs.map((p) => [p, p])),
});

const inject = args.length > 0;
const extend = type.isClass();

const writeFactory = () => {
const writeAsync = () => {
writer.conditionalWrite(creates.async, 'async ');
writer.write(`(${creates.manualArgs.join(', ')}) => `);
};

const writeArgs = () => {
writer.write(`(${creates.manualArgs.join(', ')})`);
};

const writeFactory = () => {
this.compileCall(
writer,
join(
Expand All @@ -346,19 +375,38 @@ export class Compiler {
);
};

const writeArrow = () => {
writeAsync();
writeArgs();
writer.write(' => ');
writeFactory();
};

writer.write(`factory: `);
writer.write(inject ? '(di) => ' : '() => ');

if (creates.method) {
if (!creates.method) {
writeArrow();
} else if (extend) {
writer.write(`new class extends ${join('.', source, path, object && 'factory')} {`);
writer.indent(() => {
writeAsync();
writer.write(creates.method!);
writeArgs();
writer.block(() => {
writer.write('return ');
writeFactory();
});
});
writer.write('}');
} else {
writer.write('({\n');
writer.indent(() => {
writer.write(`${creates.method}: `);
writeFactory();
writeArrow();
writer.write(',\n');
});
writer.write('})');
} else {
writeFactory();
}

writer.write(',\n');
Expand Down
43 changes: 40 additions & 3 deletions core/cli/src/definitionScanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,34 @@ export class DefinitionScanner {
}

private scanClassDeclaration(ctx: ScanContext, node: ClassDeclaration): void {
if (node.isAbstract() || node.getTypeParameters().length) {
if (node.getTypeParameters().length) {
return;
}

if (node.isAbstract()) {
if (node.getStaticMembers().length || node.getConstructors().length) {
this.logger.warning(`Abstract class ${node.getName()} has static members or a constructor`);
return;
}

for (const member of node.getInstanceMembers()) {
if (Node.isPropertyDeclaration(member)) {
if (!member.isReadonly() || !member.hasInitializer()) {
return;
}
} else if (!Node.isMethodDeclaration(member) || !member.isAbstract()) {
return;
} else {
const name = member.getName();
const params = member.getParameters();

if (name !== 'create' && (name !== 'get' || params.length !== 0)) {
return;
}
}
}
}

this.registerService(ctx, node.getType(), this.helper.resolveClassTypes(node));
}

Expand Down Expand Up @@ -245,13 +269,26 @@ export class DefinitionScanner {
if (!definition && type.isClass()) {
const symbol = type.getSymbolOrThrow();
const declaration = symbol.getValueDeclarationOrThrow();
return [this.resolveFactoryInfo(symbol.getTypeAtLocation(declaration)), false];

return Node.isClassDeclaration(declaration) && declaration.isAbstract()
? [undefined, false]
: [this.resolveFactoryInfo(symbol.getTypeAtLocation(declaration)), false];
}

const [factory, object] = Node.isObjectLiteralExpression(definition)
? [definition.getPropertyOrThrow('factory'), true]
: [definition, false];
return [factory && this.resolveFactoryInfo(factory.getType()), object];

if (!factory) {
return [undefined, object];
}

const factoryType = factory.getType();
const declaration = factoryType.getSymbol()?.getValueDeclaration();

return Node.isClassDeclaration(declaration) && declaration.isAbstract()
? [undefined, object]
: [this.resolveFactoryInfo(factoryType), object];
}

private resolveFactoryInfo(factoryType: Type): ServiceFactoryInfo | undefined {
Expand Down
7 changes: 7 additions & 0 deletions core/cli/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export namespace debug {
scope: 'private',
anonymous: true,
} satisfies ServiceDefinition<ConsoleHandler, Plugin>;

export const otherConsole = {
factory: (argv: Argv) => new ConsoleHandler({ threshold: argv.logLevel, timestamp: false }),
scope: 'private',
anonymous: true,
} satisfies ServiceDefinition<ConsoleHandler, Plugin>;
}

export const config = {
Expand All @@ -30,6 +36,7 @@ export const config = {
export const project = {
factory: (config: DiccConfig) => new Project({
tsConfigFilePath: config.project,
skipAddingFilesFromTsConfig: true,
manipulationSettings: {
indentationText: IndentationText.TwoSpaces,
newLineKind: NewLineKind.LineFeed,
Expand Down
2 changes: 1 addition & 1 deletion core/cli/src/sourceFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class SourceFiles {
for (const [outputPath, options] of Object.entries(config.containers)) {
const inputs = new Map(Object.entries(options.resources).map(([resource, opts]) => [
resource,
project.getSourceFiles(createSourceGlobs(resource, opts?.exclude ?? [])),
project.addSourceFilesAtPaths(createSourceGlobs(resource, opts?.exclude ?? [])),
]));

const output = project.createSourceFile(outputPath, createEmptyOutput(options.className), {
Expand Down
30 changes: 23 additions & 7 deletions core/cli/src/typeHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,9 @@ export class TypeHelper {
&& !declaration.hasModifier(SyntaxKind.PrivateKeyword)
&& !declaration.hasModifier(SyntaxKind.ProtectedKeyword)
} catch {
return true; // this would happen if a class has no explicit constructor -
// in that case we'd get a construct signature, but no declaration
// This would happen if a class has no explicit constructor -
// in that case we'd get a construct signature, but no declaration.
return true;
}
});

Expand All @@ -214,16 +215,31 @@ export class TypeHelper {
}

resolveAutoFactorySignature(type: Type): [signature?: Signature, method?: string] {
const [prop, ...rest] = type.getProperties();
const props = type.getProperties();

if (!prop) {
if (!props.length) {
return [this.getFirstSignature(type.getCallSignatures(), type, false)];
} else if (prop.getName() !== 'create' || rest.length) {
}

const method = props.find((prop) => /^(get|create)$/.test(prop.getName()));

if (!method) {
console.log(`NO GET/CREATE: ${type.getSymbol()?.getFullyQualifiedName()}`);
return [];
}

const name = method.getName();
const signature = this.getFirstSignature(
method.getTypeAtLocation(method.getDeclarations()[0]).getCallSignatures(),
type,
false,
);

if (!signature || (name === 'get' && signature.getParameters().length > 0)) {
return [];
}

const create = prop.getTypeAtLocation(prop.getDeclarations()[0]);
return [this.getFirstSignature(create.getCallSignatures(), type, false), 'create'];
return [signature, name];
}

resolveArgumentInfo(symbol: Symbol): ArgumentInfo {
Expand Down
Loading

0 comments on commit faf3aba

Please sign in to comment.