diff --git a/src/Compiler/Jest/HcJestTransformer.ts b/src/Compiler/Jest/HcJestTransformer.ts index ca114a5..7d70486 100644 --- a/src/Compiler/Jest/HcJestTransformer.ts +++ b/src/Compiler/Jest/HcJestTransformer.ts @@ -1,4 +1,4 @@ -import { FeatureModuleDiscoverer } from '../../Util/Feature/FeatureModuleDiscoverer'; +import { FeatureModuleDiscoverer, type FeatureSourcePath } from '../../Util/Feature/FeatureModuleDiscoverer'; import type { AsyncTransformer, TransformedSource } from '@jest/transform'; import { hash } from 'node:crypto'; import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; @@ -72,12 +72,12 @@ export class HcJestTransformer implements AsyncTransformer { sourcePath = FsHelper.normalizePathSep(sourcePath); - const featureName = this.extractFeatureNameFromPath(sourcePath); - if (!featureName || !this.featureTsTransformer.supports(sourcePath, featureName)) { + const featureSourcePath = this.extractFeatureNameFromPath(sourcePath); + if (!featureSourcePath || !this.featureTsTransformer.supports(featureSourcePath)) { return this.tsJestTransformer.processAsync(sourceText, sourcePath, options); } - return this.processFeatureSourceFile(featureName, sourceText, sourcePath, options); + return this.processFeatureSourceFile(featureSourcePath, sourceText, options); } - private processFeatureSourceFile(featureName: string, sourceText: string, sourcePath: string, options: TsJestTransformOptions): TransformedSource { + private processFeatureSourceFile(featureSourcePath: FeatureSourcePath, sourceText: string, options: TsJestTransformOptions): TransformedSource { this.setupTransformedTmpDir(options); const inSourceFile = ts.createSourceFile( - sourcePath, + featureSourcePath.sourcePath, sourceText, this.compilerOptions.target ?? ts.ScriptTarget.Latest ); @@ -107,7 +107,7 @@ export class HcJestTransformer implements AsyncTransformer ts.SourceFile): ts.TransformerFactory { - return (context: ts.TransformationContext) => transformer; + public createTransformerFactory(transformer: (sourceFile: ts.SourceFile, context: ts.TransformationContext,) => ts.SourceFile): ts.TransformerFactory { + return (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => transformer(sourceFile, context); + } + + + public transformExistingAndReturnAsString(sourceFilePath: string, transformers: ts.TransformerFactory[]): string { + const sourceFile = this.createSourceFileFromExisting(sourceFilePath); + return this.transformAndReturnAsString(sourceFile, transformers); } public transformAndReturnAsString(sourceFile: ts.SourceFile, transformers: ts.TransformerFactory[]): string { @@ -63,7 +70,6 @@ export class TsTransformerTestHelper { public transpileModule(transformer: ContextAwareCustomTransformer, sourceFilePath: string, sourceText?: string): ts.TranspileOutput { const sourceFile = sourceText ? this.createSourceFile(sourceFilePath, sourceText) : this.createSourceFileFromExisting(sourceFilePath); - return ts.transpileModule(sourceFile.text, { compilerOptions: this.compilerOptions, fileName: sourceFilePath, diff --git a/src/Compiler/Transformer/Feature/AbstractFeatureTsTransformer.ts b/src/Compiler/Transformer/Feature/AbstractFeatureTsTransformer.ts index 2ca9698..e8fcfb4 100644 --- a/src/Compiler/Transformer/Feature/AbstractFeatureTsTransformer.ts +++ b/src/Compiler/Transformer/Feature/AbstractFeatureTsTransformer.ts @@ -3,6 +3,7 @@ import type { FeatureMap, FeatureMeta } from "../../../Util/Feature/Meta/Feature import { ModuleClassTsTransformer } from '../ModuleClassTsTransformer'; import type { TsImportHelper } from '../TsImportHelper'; import type { FeatureTransformContext } from './FeatureTransformContext'; +import type { FeatureSourcePath } from "../../../Util/Feature/FeatureModuleDiscoverer"; export interface FeatureTsTransformerHelpers { importHelper: TsImportHelper; @@ -23,6 +24,6 @@ export abstract class AbstractFeatureTsTransformer { public abstract transform(source: ts.SourceFile, context: FeatureTransformContext): ts.SourceFile; - public abstract supports(sourceFilePath: string, feature: FeatureMeta): boolean; + public abstract supports(featureSourcePath: FeatureSourcePath, feature: FeatureMeta): boolean; } \ No newline at end of file diff --git a/src/Compiler/Transformer/Feature/FeatureInfraDomainTsTransformer.ts b/src/Compiler/Transformer/Feature/FeatureInfraDomainTsTransformer.ts index 078614a..f239ae2 100644 --- a/src/Compiler/Transformer/Feature/FeatureInfraDomainTsTransformer.ts +++ b/src/Compiler/Transformer/Feature/FeatureInfraDomainTsTransformer.ts @@ -4,6 +4,7 @@ import { AbstractFeatureTsTransformer } from './AbstractFeatureTsTransformer'; import { TsTransfromerHelper } from '../TsTransformerHelper'; import type { AddImportTransformDef } from '../ModuleClassTsTransformer'; import type { FeatureTransformContext } from './FeatureTransformContext'; +import type { FeatureSourcePath } from "../../../Util/Feature/FeatureModuleDiscoverer"; export class FeatureInfraDomainModuleTsTransformer extends AbstractFeatureTsTransformer { @@ -43,14 +44,14 @@ export class FeatureInfraDomainModuleTsTransformer extends AbstractFeatureTsTran ts.factory.createPropertyAssignment("domainErrors", ts.factory.createIdentifier(domainErrorsClassName)), ]); - const createMeta = TsTransfromerHelper.createConstStatement("HcDomainInfraModuleMetaExtra", ts.factory.createCallExpression( + const createMeta = TsTransfromerHelper.createConstStatement("HcDomainInfraModuleMetaExtra", undefined, ts.factory.createCallExpression( ts.factory.createPropertyAccessExpression(classIdentifier, methodIdentifier), undefined, [optionsObject] )); return [ - TsTransfromerHelper.createConstStatement("HcDomainInfraAggrgateRootRepositories", + TsTransfromerHelper.createConstStatement("HcDomainInfraAggrgateRootRepositories", undefined, ts.factory.createArrayLiteralExpression(repos.map((r) => ts.factory.createIdentifier(r))) ), createMeta @@ -65,7 +66,7 @@ export class FeatureInfraDomainModuleTsTransformer extends AbstractFeatureTsTran }); } - public supports(sourcefilePath: string, feature: FeatureMeta): boolean { - return sourcefilePath.endsWith("DomainInfraModule.ts"); + public supports(featureSourcePath: FeatureSourcePath, feature: FeatureMeta): boolean { + return featureSourcePath.localSourcePath.endsWith("DomainInfraModule.ts"); } } \ No newline at end of file diff --git a/src/Compiler/Transformer/Feature/FeatureModuleTsTransformer.ts b/src/Compiler/Transformer/Feature/FeatureModuleTsTransformer.ts index 2e11cdf..c7b0810 100644 --- a/src/Compiler/Transformer/Feature/FeatureModuleTsTransformer.ts +++ b/src/Compiler/Transformer/Feature/FeatureModuleTsTransformer.ts @@ -4,6 +4,7 @@ import { AbstractFeatureTsTransformer } from "./AbstractFeatureTsTransformer"; import type { ProviderModuleMetaTransformDef } from '../ModuleClassTsTransformer'; import type { FeatureTransformContext } from './FeatureTransformContext'; import type { FeatureApplicationMessageMeta } from '../../../Util/Feature/Meta'; +import type { FeatureSourcePath } from "../../../Util/Feature/FeatureModuleDiscoverer"; /** * Adding automatic injection of message handlers, services, infra module to `[Feature]Module` source. @@ -11,8 +12,8 @@ import type { FeatureApplicationMessageMeta } from '../../../Util/Feature/Meta'; */ export class FeatureModuleTsTransformer extends AbstractFeatureTsTransformer { - public supports(sourcefilePath: string, feature: FeatureMeta): boolean { - return sourcefilePath.endsWith(feature.name + "Module.ts"); + public supports(featureSourcePath: FeatureSourcePath, feature: FeatureMeta): boolean { + return featureSourcePath.localSourcePath.endsWith(feature.name + "Module.ts"); } public transform(source: ts.SourceFile, context: FeatureTransformContext): ts.SourceFile { diff --git a/src/Compiler/Transformer/Feature/FeatureTransformContext.ts b/src/Compiler/Transformer/Feature/FeatureTransformContext.ts index 6da0e66..922da09 100644 --- a/src/Compiler/Transformer/Feature/FeatureTransformContext.ts +++ b/src/Compiler/Transformer/Feature/FeatureTransformContext.ts @@ -1,9 +1,10 @@ import type ts from "typescript"; import type { FeatureMeta } from "../../../Util/Feature/Meta/FeatureMeta"; +import type { FeatureSourcePath } from "../../../Util/Feature/FeatureModuleDiscoverer"; export interface FeatureTransformContext { + featureSourcePath: FeatureSourcePath; feature: FeatureMeta; - sourceFilePathWithoutRoot: string; tsContext: ts.TransformationContext; diagnostics: ts.Diagnostic[]; } \ No newline at end of file diff --git a/src/Compiler/Transformer/Feature/FeatureTsTransformer.ts b/src/Compiler/Transformer/Feature/FeatureTsTransformer.ts index 4f4469f..51826bd 100644 --- a/src/Compiler/Transformer/Feature/FeatureTsTransformer.ts +++ b/src/Compiler/Transformer/Feature/FeatureTsTransformer.ts @@ -7,10 +7,11 @@ import type { AbstractFeatureTsTransformer } from "./AbstractFeatureTsTransforme import { FeatureInfraDomainModuleTsTransformer } from "./FeatureInfraDomainTsTransformer"; import { FeatureModuleTsTransformer } from "./FeatureModuleTsTransformer"; import path from "node:path"; -import { FeatureModuleDiscoverer } from "../../../Util/Feature/FeatureModuleDiscoverer"; +import { FeatureModuleDiscoverer, type FeatureSourcePath } from "../../../Util/Feature/FeatureModuleDiscoverer"; import { TsImportHelper } from "../TsImportHelper"; import { ModuleClassTsTransformer } from "../ModuleClassTsTransformer"; import type { FeatureTransformContext } from "./FeatureTransformContext"; +//import { HObjectTsTransformer } from "./HObject/HObjectTsTransformer"; /** @@ -35,6 +36,7 @@ export class FeatureTsTransformer { this.transformers = [ new FeatureInfraDomainModuleTsTransformer(helpers), new FeatureModuleTsTransformer(helpers), + //new HObjectTsTransformer(helpers), ]; } @@ -61,22 +63,15 @@ export class FeatureTsTransformer { } public transform(source: ts.SourceFile, context: ts.TransformationContext): ts.SourceFile { - const sourceFilePath = FsHelper.normalizePathSep(source.fileName); - const feature = this.getFeatureOfPath(sourceFilePath); - if (!feature) { + const featureSourceTransformContext = this.createFeatureSourceTransformContext(source, context); + if (!featureSourceTransformContext) { return source; } - const sourceFilePathWithoutRoot = sourceFilePath.substring(this.sourceRoot.length + 1); - const transformContext: FeatureTransformContext = { - feature, - sourceFilePathWithoutRoot, - tsContext: context, - diagnostics: [], - }; + for (const t of this.transformers) { - if (t.supports(sourceFilePathWithoutRoot, feature)) { - const transformed = t.transform(source, transformContext); + if (t.supports(featureSourceTransformContext.featureSourcePath, featureSourceTransformContext.feature)) { + const transformed = t.transform(source, featureSourceTransformContext); return transformed; } } @@ -84,19 +79,26 @@ export class FeatureTsTransformer { return source; } - private getFeatureOfPath(sourceFilePath: string): FeatureMeta | null { - const featureName = FeatureModuleDiscoverer.extractFeatureNameFromPath(this.sourceRoot, sourceFilePath); - if (!featureName) { + private createFeatureSourceTransformContext(source: ts.SourceFile, context: ts.TransformationContext): FeatureTransformContext | null { + const sourceFilePath = FsHelper.normalizePathSep(source.fileName); + const featureSourcePath = FeatureModuleDiscoverer.extractFeatureNameFromPath(this.sourceRoot, sourceFilePath); + if (!featureSourcePath) { return null; } - return this.features.get(featureName) as FeatureMeta; + const feature = this.features.get(featureSourcePath.featureName)!; + return { + feature, + featureSourcePath, + tsContext: context, + diagnostics: [], + }; } - public supports(sourceFilePath: string, featureName: string): boolean { - const feature = this.features.get(featureName)!; + public supports(sourcePath: FeatureSourcePath): boolean { + const feature = this.features.get(sourcePath.featureName)!; for (const t of this.transformers) { - if (t.supports(sourceFilePath, feature)) { + if (t.supports(sourcePath, feature)) { return true; } } diff --git a/src/Compiler/Transformer/Helper/ImportDeclarationWrapper.ts b/src/Compiler/Transformer/Helper/ImportDeclarationWrapper.ts new file mode 100644 index 0000000..5255d50 --- /dev/null +++ b/src/Compiler/Transformer/Helper/ImportDeclarationWrapper.ts @@ -0,0 +1,61 @@ +import ts from "typescript"; +import { TsTransfromerHelper } from "../TsTransformerHelper"; + +export class ImportDeclarationWrapper { + public constructor( + public declaration: ts.ImportDeclaration, + private context: ts.TransformationContext + ) { + + } + + public static create(importName: string | string[], importPath: string, context: ts.TransformationContext): ImportDeclarationWrapper { + return ImportDeclarationWrapper.from(TsTransfromerHelper.createImportDeclaration(importName, importPath), context); + } + + public static from(importDecl: ts.ImportDeclaration, context: ts.TransformationContext): ImportDeclarationWrapper { + return new this(importDecl, context); + } + + public get(name: string): ts.PropertyAccessChain | ts.Identifier { + return TsTransfromerHelper.createNamedImportAccess(this.declaration, name, this.context); + } + + public hasNamedAccess(name: string): boolean { + if (!this.declaration.importClause?.namedBindings) { + return false; + } + + return (this.declaration.importClause.namedBindings as ts.NamedImports).elements.filter(i => i.name.text === name).length > 0; + } + + public getEntityName(name: string): ts.EntityName { + const current = this.get(name); + if (ts.isIdentifier(current)) { + return current; + } + + return ts.factory.createQualifiedName(current.expression as ts.Identifier, current.name as ts.Identifier); + } + + public addNamedImports(elements: (ts.ImportSpecifier | string)[]): ts.ImportDeclaration { + const newNamedImports = ts.factory.createNamedImports([ + ...(this.declaration.importClause?.namedBindings as ts.NamedImports).elements.map(e => ts.factory.createImportSpecifier(e.isTypeOnly, e.propertyName, ts.factory.createIdentifier(e.name.text))), + ...elements.map(e => typeof e === 'string' ? ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(e)) : e), + ]); + + this.declaration = ts.factory.updateImportDeclaration( + this.declaration, + undefined, + ts.factory.updateImportClause(this.declaration.importClause!, + this.declaration.importClause!.isTypeOnly, undefined, newNamedImports), + this.declaration.moduleSpecifier, this.declaration.attributes + ); + + return this.declaration; + } + + public get importNames(): string[] { + return (this.declaration.importClause?.namedBindings as ts.NamedImports).elements.map(e => e.name.text); + } +} \ No newline at end of file diff --git a/src/Compiler/Transformer/ModuleClassTsTransformer.ts b/src/Compiler/Transformer/ModuleClassTsTransformer.ts index c66cd1d..661bae4 100644 --- a/src/Compiler/Transformer/ModuleClassTsTransformer.ts +++ b/src/Compiler/Transformer/ModuleClassTsTransformer.ts @@ -170,7 +170,7 @@ export class ModuleClassTsTransformer { private createProviderDeclarationStatement(def: ProviderModuleMetaTransformDef): ts.VariableStatement { const varName = def.name + "Provider"; - return TsTransfromerHelper.createConstStatement(varName, def.expression!); + return TsTransfromerHelper.createConstStatement(varName, undefined, def.expression!); } private getArrayPropertyAsSpread(identifier: ts.Identifier, property: string) { diff --git a/src/Compiler/Transformer/TsTransformerHelper.ts b/src/Compiler/Transformer/TsTransformerHelper.ts index e145606..d1b8589 100644 --- a/src/Compiler/Transformer/TsTransformerHelper.ts +++ b/src/Compiler/Transformer/TsTransformerHelper.ts @@ -1,56 +1,11 @@ import { Module } from "@nestjs/common"; -import ts, { type NamedImports } from "typescript"; +import ts from "typescript"; import { TsImportHelper } from "./TsImportHelper"; export type ImportFromMapper = (importFrom: string) => string; export type ImportedIdentifierMapper = (identifier: string) => ts.PropertyAccessChain | ts.Identifier; -export class ImportDeclarationWrapper { - public constructor(private importDecl: ts.ImportDeclaration, private context: ts.TransformationContext) { - - } - - public static from(importDecl: ts.ImportDeclaration, context: ts.TransformationContext): ImportDeclarationWrapper { - return new this(importDecl, context); - } - - public get(name: string): ts.PropertyAccessChain | ts.Identifier { - return TsTransfromerHelper.createNamedImportAccess(this.importDecl, name, this.context); - } - - public hasNamedAccess(name: string): boolean { - if (!this.importDecl.importClause?.namedBindings) { - return false; - } - - return (this.importDecl.importClause.namedBindings as NamedImports).elements.filter(i => i.name.text === name).length > 0; - } - - public getEntityName(name: string): ts.EntityName { - const current = this.get(name); - if (ts.isIdentifier(current)) { - return current; - } - - return ts.factory.createQualifiedName(current.expression as ts.Identifier, current.name as ts.Identifier); - } - - public addNamedImports(elements: (ts.ImportSpecifier | string)[]): ts.ImportDeclaration { - const newNamedImports = ts.factory.createNamedImports([ - ...(this.importDecl.importClause?.namedBindings as ts.NamedImports).elements, - ...elements.map(e => typeof e === 'string' ? ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(e)) : e), - ]); - return ts.factory.updateImportDeclaration( - this.importDecl, - undefined, - ts.factory.updateImportClause(this.importDecl.importClause!, - this.importDecl.importClause!.isTypeOnly, undefined, newNamedImports), - this.importDecl.moduleSpecifier, this.importDecl.attributes - ); - } -} - export class TsTransfromerHelper { private static printer: ts.Printer; @@ -174,11 +129,28 @@ export class TsTransfromerHelper { ); } - public static createConstStatement(name: string, initializer: ts.Expression, type?: ts.TypeNode): ts.VariableStatement { - return ts.factory.createVariableStatement([], ts.factory.createVariableDeclarationList( - [ts.factory.createVariableDeclaration(name, undefined, type, initializer)], - ts.NodeFlags.Const, - )); + /** + * `let name = initializer` + * `let name` + * `let name: type` + * `let name: type = initializer` + */ + public static createLetStatement(name: string, type?: ts.TypeNode, initializer?: ts.Expression): ts.VariableStatement { + return ts.factory.createVariableStatement(undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration(name, undefined, type, initializer)], + ts.NodeFlags.Let, + ) + ); + } + + public static createConstStatement(name: string, type?: ts.TypeNode, initializer?: ts.Expression): ts.VariableStatement { + return ts.factory.createVariableStatement(undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration(name, undefined, type, initializer)], + ts.NodeFlags.Const, + ) + ); } public static createImportFromMapper(sourceRoot: string): ImportFromMapper { diff --git a/src/Util/Feature/FeatureModuleDiscoverer.ts b/src/Util/Feature/FeatureModuleDiscoverer.ts index 81e1a6e..42f7d2c 100644 --- a/src/Util/Feature/FeatureModuleDiscoverer.ts +++ b/src/Util/Feature/FeatureModuleDiscoverer.ts @@ -8,7 +8,11 @@ import { FeatureDomainDiscoverer } from "./FeatureDomainDiscoverer"; import { FeatureInfrastructureDiscoverer } from "./FeatureInfrastructureDiscoverer"; import { FeatureMeta } from "./Meta/FeatureMeta"; -export const CORE_FEATURE_NAME = "Core"; +export interface FeatureSourcePath { + featureName: string; + localSourcePath: string; + sourcePath: string; +} export class FeatureModuleDiscoverer { @@ -24,18 +28,22 @@ export class FeatureModuleDiscoverer { this.infrastructureDiscoverer = new FeatureInfrastructureDiscoverer(); } - public static extractFeatureNameFromPath(sourceRoot: string, sourcePath: string): string | null { + public static extractFeatureNameFromPath(sourceRoot: string, sourcePath: string): FeatureSourcePath | null { if (!sourcePath.startsWith(sourceRoot)) { return null; } - const withoutSourceRootParts = sourcePath.substring(sourceRoot.length + 1).split("/", 2); + const withoutRoot = sourcePath.substring(sourceRoot.length + 1); + const withoutSourceRootParts = withoutRoot.split("/", 2); if (withoutSourceRootParts.length < 2) { return null; } - const featureName = withoutSourceRootParts[0]; - return featureName === CORE_FEATURE_NAME ? null : featureName; + return { + featureName: withoutSourceRootParts[0], + sourcePath, + localSourcePath: withoutRoot.substring(withoutSourceRootParts[0].length + 1), + }; } public async discoverAll(): Promise> { @@ -43,10 +51,6 @@ export class FeatureModuleDiscoverer { const features = new Map(); for (const featureDir of featureDirs) { const featureName = path.basename(featureDir); - if (featureName === CORE_FEATURE_NAME) { - continue; - } - const discovery = await this.discover(featureName, featureDir); features.set(featureName, discovery); }