Skip to content
Draft
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
25 changes: 25 additions & 0 deletions packages/compiler/src/config/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,30 @@ export const TypeSpecConfigJsonSchema: JSONSchemaType<TypeSpecRawConfig> = {
},
},
} as any, // ajv type system doesn't like the string templates
transformer: {
type: "object",
nullable: true,
required: [],
additionalProperties: false,
properties: {
extends: {
type: "array",
nullable: true,
items: { type: "string" },
},
enable: {
type: "object",
required: [],
nullable: true,
additionalProperties: { type: "boolean" },
},
disable: {
type: "object",
required: [],
nullable: true,
additionalProperties: { type: "string" },
},
},
} as any, // ajv type system doesn't like the string templates
},
};
9 changes: 9 additions & 0 deletions packages/compiler/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ export interface TypeSpecConfig {
options?: Record<string, EmitterOptions>;

linter?: LinterConfig;

transformer?: TransformerConfig;
}

/**
Expand All @@ -88,6 +90,7 @@ export interface TypeSpecRawConfig {
options?: Record<string, EmitterOptions>;

linter?: LinterConfig;
transformer?: TransformerConfig;
}

export interface ConfigEnvironmentVariable {
Expand All @@ -107,3 +110,9 @@ export interface LinterConfig {
enable?: Record<RuleRef, boolean>;
disable?: Record<RuleRef, string>;
}

export interface TransformerConfig {
extends?: RuleRef[];
enable?: Record<RuleRef, boolean>;
disable?: Record<RuleRef, string>;
}
12 changes: 12 additions & 0 deletions packages/compiler/src/core/library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
LinterRuleDefinition,
PackageFlags,
StateDef,
TransformDefinition,
TransformerDefinition,
TypeSpecLibrary,
TypeSpecLibraryDef,
} from "./types.js";
Expand Down Expand Up @@ -116,6 +118,16 @@ export function createLinterRule<const N extends string, const T extends Diagnos
return definition;
}

export function defineTransformer(def: TransformerDefinition): TransformerDefinition {
return def;
}

/** Create a new transform. */
export function createTransform<const N extends string>(definition: TransformDefinition<N>) {
compilerAssert(!definition.name.includes("/"), "Transform name cannot contain a '/'.");
return definition;
}

/**
* Set the TypeSpec namespace for that function.
* @param namespace Namespace string (e.g. "Foo.Bar")
Expand Down
5 changes: 4 additions & 1 deletion packages/compiler/src/core/options.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EmitterOptions, TypeSpecConfig } from "../config/types.js";
import { LinterRuleSet, ParseOptions } from "./types.js";
import { LinterRuleSet, ParseOptions, TransformSet } from "./types.js";

export interface CompilerOptions {
miscOptions?: Record<string, unknown>;
Expand Down Expand Up @@ -68,6 +68,9 @@ export interface CompilerOptions {
/** Ruleset to enable for linting. */
linterRuleSet?: LinterRuleSet;

/** Transform set to enable for transformation. */
transformSet?: TransformSet;

/** @internal */
readonly configFile?: TypeSpecConfig;
}
37 changes: 32 additions & 5 deletions packages/compiler/src/core/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ import {
} from "./source-loader.js";
import { createStateAccessors } from "./state-accessors.js";
import { ComplexityStats, RuntimeStats, Stats, startTimer, time, timeAsync } from "./stats.js";
import {
builtInTransformerLibraryName,
createBuiltInTransformerLibrary,
createTransformer,
resolveTransformerDefinition,
} from "./transformer.js";
import {
CompilerHost,
Diagnostic,
Expand All @@ -52,8 +58,8 @@ import {
EmitterFunc,
Entity,
JsSourceFileNode,
LibraryInstance,
LibraryMetadata,
LinterLibraryInstance,
LiteralType,
LocationContext,
LogSink,
Expand All @@ -68,6 +74,7 @@ import {
SyntaxKind,
TemplateInstanceTarget,
Tracer,
TransformerLibraryInstance,
Type,
TypeSpecLibrary,
TypeSpecScriptNode,
Expand Down Expand Up @@ -133,13 +140,15 @@ export interface Program {
readonly projectRoot: string;
}

export interface TransformedProgram extends Program {}

interface EmitterRef {
emitFunction: EmitterFunc;
main: string;
metadata: LibraryMetadata;
emitterOutputDir: string;
options: Record<string, unknown>;
readonly library: LibraryInstance;
readonly library: LinterLibraryInstance & TransformerLibraryInstance;
}

interface Validator {
Expand Down Expand Up @@ -204,7 +213,7 @@ async function createProgram(
mainFile: string,
options: CompilerOptions = {},
oldProgram?: Program,
): Promise<{ program: Program; shouldAbort: boolean }> {
): Promise<{ program: TransformedProgram; shouldAbort: boolean }> {
const runtimeStats: Partial<RuntimeStats> = {};
const validateCbs: Validator[] = [];
const stateMaps = new Map<symbol, Map<Type, unknown>>();
Expand Down Expand Up @@ -299,6 +308,16 @@ async function createProgram(
program.reportDiagnostics(await linter.extendRuleSet(options.linterRuleSet));
}

const transformer = createTransformer(program, (name) => loadLibrary(basedir, name));
// Register built-in transformer library (currently empty placeholder)
transformer.registerTransformLibrary(
builtInTransformerLibraryName,
createBuiltInTransformerLibrary(),
);
if (options.transformSet) {
program.reportDiagnostics(await transformer.extendTransformSet(options.transformSet));
}

program.checker = createChecker(program, resolver);
runtimeStats.checker = time(() => program.checker.checkProgram());

Expand All @@ -325,7 +344,12 @@ async function createProgram(
runtimeStats.linter = lintResult.stats.runtime;
program.reportDiagnostics(lintResult.diagnostics);

return { program, shouldAbort: false };
// Transform stage
const transformResult = transformer.transform();
runtimeStats.transformer = transformResult.stats.runtime;
program.reportDiagnostics(transformResult.diagnostics);

return { program: transformResult.program, shouldAbort: false };

/**
* Validate the libraries loaded during the compilation process are compatible.
Expand Down Expand Up @@ -503,7 +527,7 @@ async function createProgram(
async function loadLibrary(
basedir: string,
libraryNameOrPath: string,
): Promise<LibraryInstance | undefined> {
): Promise<(LinterLibraryInstance & TransformerLibraryInstance) | undefined> {
const [resolution, diagnostics] = await resolveEmitterModuleAndEntrypoint(
basedir,
libraryNameOrPath,
Expand All @@ -518,11 +542,14 @@ async function createProgram(
const libDefinition: TypeSpecLibrary<any> | undefined = entrypoint?.esmExports.$lib;
const metadata = computeLibraryMetadata(module, libDefinition);
const linterDef = entrypoint?.esmExports.$linter;
const transformerDef = entrypoint?.esmExports.$transformer;
return {
...resolution,
metadata,
definition: libDefinition,
linter: linterDef && resolveLinterDefinition(libraryNameOrPath, linterDef),
transformer:
transformerDef && resolveTransformerDefinition(libraryNameOrPath, transformerDef),
};
}

Expand Down
6 changes: 6 additions & 0 deletions packages/compiler/src/core/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export interface RuntimeStats {
[rule: string]: number;
};
};
transformer: {
total: number;
transforms: {
[transform: string]: number;
};
};
emit: {
total: number;
emitters: {
Expand Down
Loading