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
3 changes: 2 additions & 1 deletion packages/rlc-common/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
}
],
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error"
"@typescript-eslint/no-misused-promises": "error",
"no-console": "error"
}
}
3 changes: 2 additions & 1 deletion packages/typespec-ts/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
],
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/no-deprecated": "warn"
"@typescript-eslint/no-deprecated": "warn",
"no-console": "error"
}
}
16 changes: 14 additions & 2 deletions packages/typespec-ts/src/framework/load-static-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { refkey } from "./refkey.js";
import { resolveProjectRoot } from "../utils/resolve-project-root.js";
import { isAzurePackage } from "@azure-tools/rlc-common";
import { ModularEmitterOptions } from "../modular/interfaces.js";
import { NoTarget, Program } from "@typespec/compiler";
import { reportDiagnostic } from "../lib.js";
export const SourceFileSymbol = Symbol("SourceFile");
export interface StaticHelperMetadata {
name: string;
Expand Down Expand Up @@ -41,6 +43,7 @@ export interface LoadStaticHelpersOptions
extends Partial<ModularEmitterOptions> {
helpersAssetDirectory?: string;
sourcesDir?: string;
program?: Program;
}

export async function loadStaticHelpers(
Expand All @@ -55,7 +58,8 @@ export async function loadStaticHelpers(
DEFAULT_STATIC_HELPERS_PATH
);
const files = await traverseDirectory(
options.helpersAssetDirectory ?? defaultStaticHelpersPath
options.helpersAssetDirectory ?? defaultStaticHelpersPath,
options.program
);

for (const file of files) {
Expand Down Expand Up @@ -161,6 +165,7 @@ function getDeclarationByMetadata(
const _targetStaticHelpersBaseDir = "static-helpers";
async function traverseDirectory(
directory: string,
program?: Program,
result: { source: string; target: string }[] = [],
relativePath: string = ""
): Promise<{ source: string; target: string }[]> {
Expand All @@ -175,6 +180,7 @@ async function traverseDirectory(
if (fileStat.isDirectory()) {
await traverseDirectory(
filePath,
program,
result,
path.join(relativePath, file)
);
Expand All @@ -195,7 +201,13 @@ async function traverseDirectory(

return result;
} catch (error) {
console.error(`Error traversing directory ${directory}:`, error);
if (program) {
reportDiagnostic(program, {
code: "directory-traversal-error",
format: { directory, error: String(error) },
target: NoTarget
});
}
throw error;
}
}
6 changes: 0 additions & 6 deletions packages/typespec-ts/src/framework/sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,6 @@ sourceFile2.addStatements(`let obj: ${modelReference} = { id: 1 };`);
// Apply imports to ensure correct references
binder.resolveAllReferences("/modularPackageFolder/src");

// Output the generated files
console.log("// test.ts");
console.log(sourceFile.getFullText());
console.log("// test2.ts");
console.log(sourceFile2.getFullText());

// Output
// test.ts
// interface MyInterface {
Expand Down
26 changes: 2 additions & 24 deletions packages/typespec-ts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,14 @@ import { emitSamples } from "./modular/emitSamples.js";
export * from "./lib.js";

export async function $onEmit(context: EmitContext) {
console.time("onEmit");
if (context.program.compilerOptions.noEmit || context.program.hasError()) {
return;
}
/** Shared status */
const outputProject = new Project();
const program: Program = context.program;
const emitterOptions: EmitterOptions = context.options;
console.time("onEmit: create context");
const dpgContext = await createContextWithDefaultOptions(context);
console.timeEnd("onEmit: create context");
// Enrich the dpg context with path detail and common options
await enrichDpgContext();
const rlcOptions = dpgContext.rlcOptions ?? {};
Expand All @@ -124,7 +121,6 @@ export async function $onEmit(context: EmitContext) {
compilerContext: context,
tcgcContext: dpgContext
});
console.time("onEmit: load static helpers");
const staticHelpers = await loadStaticHelpers(
outputProject,
{
Expand All @@ -137,36 +133,32 @@ export async function $onEmit(context: EmitContext) {
},
{
sourcesDir: dpgContext.generationPathDetail?.modularSourcesDir,
options: rlcOptions
options: rlcOptions,
program
}
);
console.timeEnd("onEmit: load static helpers");
const extraDependencies = isAzurePackage({ options: rlcOptions })
? {
...AzurePollingDependencies,
...AzureCoreDependencies,
...AzureIdentityDependencies
}
: { ...DefaultCoreDependencies };
console.time("onEmit: provide binder");
const binder = provideBinder(outputProject, {
staticHelpers,
dependencies: {
...extraDependencies
}
});
provideSdkTypes(dpgContext);
console.timeEnd("onEmit: provide binder");

const rlcCodeModels: RLCModel[] = [];
let modularEmitterOptions: ModularEmitterOptions;
// 1. Clear sources folder
await clearSrcFolder();
// 2. Generate RLC code model
// TODO: skip this step in modular once modular generator is sufficiently decoupled
console.time("onEmit: build RLC code models");
await buildRLCCodeModels();
console.timeEnd("onEmit: build RLC code models");

// 4. Generate sources
if (emitterOptions["is-modular-library"]) {
Expand Down Expand Up @@ -267,7 +259,6 @@ export async function $onEmit(context: EmitContext) {
}

async function generateModularSources() {
console.time("onEmit: generate modular sources");
const modularSourcesRoot =
dpgContext.generationPathDetail?.modularSourcesDir ?? "src";
const project = useContext("outputProject");
Expand All @@ -290,13 +281,10 @@ export async function $onEmit(context: EmitContext) {
);

const isMultiClients = dpgContext.sdkPackage.clients.length > 1;
console.time("onEmit: emit models");
emitTypes(dpgContext, { sourceRoot: modularSourcesRoot });
console.timeEnd("onEmit: emit models");
buildSubpathIndexFile(modularEmitterOptions, "models", undefined, {
recursive: true
});
console.time("onEmit: emit source files");
const clientMap = getClientHierarchyMap(dpgContext);
if (clientMap.length === 0) {
// If no clients, we still need to build the root index file
Expand Down Expand Up @@ -336,27 +324,20 @@ export async function $onEmit(context: EmitContext) {
subClient
);
}
console.timeEnd("onEmit: emit source files");
// Enable modular sample generation when explicitly set to true or MPG
if (emitterOptions["generate-sample"] === true) {
console.time("onEmit: emit samples");
const samples = emitSamples(dpgContext);
console.timeEnd("onEmit: emit samples");
// Refine the rlc sample generation logic
// TODO: remember to remove this out when RLC is splitted from Modular
if (samples.length > 0) {
dpgContext.rlcOptions!.generateSample = true;
}
}

console.time("onEmit: resolve references");
binder.resolveAllReferences(modularSourcesRoot);
if (program.compilerOptions.noEmit || program.hasError()) {
return;
}
console.timeEnd("onEmit: resolve references");

console.time("onEmit: generate files");

for (const file of project.getSourceFiles()) {
await emitContentByBuilder(
Expand All @@ -365,8 +346,6 @@ export async function $onEmit(context: EmitContext) {
modularEmitterOptions as any
);
}
console.timeEnd("onEmit: generate files");
console.timeEnd("onEmit: generate modular sources");
}
interface Metadata {
apiVersion?: string;
Expand Down Expand Up @@ -541,7 +520,6 @@ export async function $onEmit(context: EmitContext) {
.map((subClient) => getClientContextPath(subClient, options))
.map((path) => path.substring(path.indexOf("src")));
}
console.timeEnd("onEmit");
}

export async function createContextWithDefaultOptions(
Expand Down
12 changes: 12 additions & 0 deletions packages/typespec-ts/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,18 @@ const libDef = {
messages: {
default: paramMessage`The parameter name ${"parameterName"} has conflicts with others and please use @clientName to rename it.`
}
},
"file-format-error": {
severity: "error",
messages: {
default: paramMessage`Failed to format file: ${"filePath"}`
}
},
"directory-traversal-error": {
severity: "error",
messages: {
default: paramMessage`Error traversing directory ${"directory"}: ${"error"}`
}
}
},
emitter: {
Expand Down
4 changes: 1 addition & 3 deletions packages/typespec-ts/src/modular/buildClientContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,7 @@ export function buildClientContext(
}`;
} else {
apiVersionPolicyStatement += `
if (options.apiVersion) {
console.warn("This client does not support client api-version, please change it at the operation level");
}`;
// This client does not support client api-version, please change it at the operation level`;
}
factoryFunction.addStatements(apiVersionPolicyStatement);

Expand Down
14 changes: 11 additions & 3 deletions packages/typespec-ts/src/utils/emitUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ import {
isAzurePackage,
RLCModel
} from "@azure-tools/rlc-common";
import { CompilerHost, Program } from "@typespec/compiler";
import { CompilerHost, NoTarget, Program } from "@typespec/compiler";
import { dirname, join } from "path";
import { format } from "prettier";
import { prettierJSONOptions, prettierTypeScriptOptions } from "../lib.js";
import {
prettierJSONOptions,
prettierTypeScriptOptions,
reportDiagnostic
} from "../lib.js";

export async function emitModels(rlcModels: RLCModel, program: Program) {
const schemaOutput = buildSchemaTypes(rlcModels);
Expand Down Expand Up @@ -78,7 +82,11 @@ async function emitFile(
isJson ? prettierJSONOptions : prettierTypeScriptOptions
);
} catch (e) {
console.error(`Failed to format file: ${filePath}`);
reportDiagnostic(program, {
code: "file-format-error",
format: { filePath },
target: NoTarget
});
throw e;
}
}
Expand Down
33 changes: 33 additions & 0 deletions packages/typespec-ts/test-next/unit/diagnostics.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { NoTarget, Program } from "@typespec/compiler";
import * as lib from "../../src/lib.js";

// Mock the reportDiagnostic function
const mockReportDiagnostic = vi.fn();

describe("Diagnostic reporting", () => {
beforeEach(() => {
mockReportDiagnostic.mockClear();
// Spy on reportDiagnostic
vi.spyOn(lib, "reportDiagnostic").mockImplementation(mockReportDiagnostic);
});

describe("file-format-error diagnostic", () => {
it("should report diagnostic when file formatting fails", async () => {
// Import the module that uses reportDiagnostic
const { default: emitUtil } = await import("../../src/utils/emitUtil.js");

// This test verifies the diagnostic code is defined
expect(lib.$lib.diagnostics["file-format-error"]).toBeDefined();
expect(lib.$lib.diagnostics["file-format-error"].severity).toBe("error");
});
});

describe("directory-traversal-error diagnostic", () => {
it("should report diagnostic when directory traversal fails", () => {
// This test verifies the diagnostic code is defined
expect(lib.$lib.diagnostics["directory-traversal-error"]).toBeDefined();
expect(lib.$lib.diagnostics["directory-traversal-error"].severity).toBe("error");
});
});
});