Use same entry point handling for compile and import (#577)
We no longer glob files when a directory is passed to `adl compile`. Instead, we will look for `adlMain` specified by package.json or main.adl in the directory. This eliminates any dependency on file system enumeration order and will make it easier for tooling to implement semantic features. Also: * Fix various issues with reporting I/O errors as diagnostics. * Fix issue where regen-samples would silently pass PR validation if nothing at all was emitted. * Make regen-samples have opt-out exclude list instead of opt-in include list. * Report correct location for syntax errors in decorators, and fix issue with copying petstore sample to another folder without type: module in package.json * Allow .mjs extension for decorator imports. * Make sure unahndled promise rejection becomes internal compiler error. * Fix issue with dumping config with diagnostics from `adl info` * Avoid repeating config filename in `adl info`
This commit is contained in:
Родитель
c62c1036f9
Коммит
8518a0bd17
|
@ -16,10 +16,10 @@ const args = yargs(process.argv.slice(2))
|
|||
.scriptName("adl")
|
||||
.help()
|
||||
.strict()
|
||||
.command("compile <path>", "Compile a directory of ADL files.", (cmd) => {
|
||||
.command("compile <path>", "Compile ADL source.", (cmd) => {
|
||||
return cmd
|
||||
.positional("path", {
|
||||
description: "The path to folder containing .adl files",
|
||||
description: "The path to the main.adl file or directory containing main.adl.",
|
||||
type: "string",
|
||||
})
|
||||
.option("output-path", {
|
||||
|
@ -40,44 +40,40 @@ const args = yargs(process.argv.slice(2))
|
|||
describe: "Don't load the ADL standard library.",
|
||||
});
|
||||
})
|
||||
.command(
|
||||
"generate <path>",
|
||||
"Generate client and server code from a directory of ADL files.",
|
||||
(cmd) => {
|
||||
return (
|
||||
cmd
|
||||
.positional("path", {
|
||||
description: "The path to folder containing .adl files",
|
||||
type: "string",
|
||||
})
|
||||
.option("client", {
|
||||
type: "boolean",
|
||||
describe: "Generate a client library for the ADL definition",
|
||||
})
|
||||
.option("language", {
|
||||
type: "string",
|
||||
choices: ["typescript", "csharp", "python"],
|
||||
describe: "The language to use for code generation",
|
||||
})
|
||||
.option("output-path", {
|
||||
type: "string",
|
||||
default: "./adl-output",
|
||||
describe:
|
||||
"The output path for generated artifacts. If it does not exist, it will be created.",
|
||||
})
|
||||
.option("option", {
|
||||
type: "array",
|
||||
string: true,
|
||||
describe:
|
||||
"Key/value pairs that can be passed to ADL components. The format is 'key=value'. This parameter can be used multiple times to add more options.",
|
||||
})
|
||||
// we can't generate anything but a client yet
|
||||
.demandOption("client")
|
||||
// and language is required to do so
|
||||
.demandOption("language")
|
||||
);
|
||||
}
|
||||
)
|
||||
.command("generate <path>", "Generate client code from ADL source.", (cmd) => {
|
||||
return (
|
||||
cmd
|
||||
.positional("path", {
|
||||
description: "The path to folder containing .adl files",
|
||||
type: "string",
|
||||
})
|
||||
.option("client", {
|
||||
type: "boolean",
|
||||
describe: "Generate a client library for the ADL definition",
|
||||
})
|
||||
.option("language", {
|
||||
type: "string",
|
||||
choices: ["typescript", "csharp", "python"],
|
||||
describe: "The language to use for code generation",
|
||||
})
|
||||
.option("output-path", {
|
||||
type: "string",
|
||||
default: "./adl-output",
|
||||
describe:
|
||||
"The output path for generated artifacts. If it does not exist, it will be created.",
|
||||
})
|
||||
.option("option", {
|
||||
type: "array",
|
||||
string: true,
|
||||
describe:
|
||||
"Key/value pairs that can be passed to ADL components. The format is 'key=value'. This parameter can be used multiple times to add more options.",
|
||||
})
|
||||
// we can't generate anything but a client yet
|
||||
.demandOption("client")
|
||||
// and language is required to do so
|
||||
.demandOption("language")
|
||||
);
|
||||
})
|
||||
.command("code", "Manage VS Code Extension.", (cmd) => {
|
||||
return cmd
|
||||
.demandCommand(1, "No command specified.")
|
||||
|
@ -261,9 +257,12 @@ async function printInfo() {
|
|||
|
||||
const config = await loadADLConfigInDir(cwd);
|
||||
const jsyaml = await import("js-yaml");
|
||||
const excluded = ["diagnostics", "filename"];
|
||||
const replacer = (key: string, value: any) => (excluded.includes(key) ? undefined : value);
|
||||
|
||||
console.log(`User Config: ${config.filename ?? "No config file found"}`);
|
||||
console.log("-----------");
|
||||
console.log(jsyaml.dump(config));
|
||||
console.log(jsyaml.dump(config, { replacer }));
|
||||
console.log("-----------");
|
||||
logDiagnostics(config.diagnostics, console.error);
|
||||
if (config.diagnostics.some((d) => d.severity === "error")) {
|
||||
|
@ -377,15 +376,20 @@ async function main() {
|
|||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
// NOTE: An expected error, like one thrown for bad input, shouldn't reach
|
||||
// here, but be handled somewhere else. If we reach here, it should be
|
||||
// considered a bug and therefore we should not suppress the stack trace as
|
||||
// that risks losing it in the case of a bug that does not repro easily.
|
||||
console.error("Internal compiler error!");
|
||||
console.error("File issue at https://github.com/azure/adl");
|
||||
dumpError(err, console.error);
|
||||
process.exit(1);
|
||||
});
|
||||
function internalCompilerError(error: Error) {
|
||||
// NOTE: An expected error, like one thrown for bad input, shouldn't reach
|
||||
// here, but be handled somewhere else. If we reach here, it should be
|
||||
// considered a bug and therefore we should not suppress the stack trace as
|
||||
// that risks losing it in the case of a bug that does not repro easily.
|
||||
console.error("Internal compiler error!");
|
||||
console.error("File issue at https://github.com/azure/adl");
|
||||
dumpError(error, console.error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.on("unhandledRejection", (error: Error) => {
|
||||
console.error("Unhandled promise rejection!");
|
||||
internalCompilerError(error);
|
||||
});
|
||||
|
||||
main().catch(internalCompilerError);
|
||||
|
|
|
@ -62,7 +62,7 @@ export const Message = {
|
|||
|
||||
FileNotFound: {
|
||||
code: 1109,
|
||||
text: `File {0} is not found.`,
|
||||
text: `File {0} not found.`,
|
||||
severity: "error",
|
||||
} as const,
|
||||
};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
export interface CompilerOptions {
|
||||
miscOptions?: any;
|
||||
mainFile?: string;
|
||||
outputPath?: string;
|
||||
swaggerOutputFile?: string;
|
||||
nostdlib?: boolean;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { dirname, extname, isAbsolute, join, resolve } from "path";
|
||||
import { dirname, extname, isAbsolute, resolve } from "path";
|
||||
import resolveModule from "resolve";
|
||||
import { createBinder, createSymbolTable } from "./binder.js";
|
||||
import { createChecker } from "./checker.js";
|
||||
|
@ -22,6 +22,7 @@ import {
|
|||
SyntaxKind,
|
||||
Type,
|
||||
} from "./types.js";
|
||||
import { doIO, loadFile } from "./util.js";
|
||||
|
||||
export interface Program {
|
||||
compilerOptions: CompilerOptions;
|
||||
|
@ -52,7 +53,8 @@ export interface Program {
|
|||
|
||||
export async function createProgram(
|
||||
host: CompilerHost,
|
||||
options: CompilerOptions
|
||||
mainFile: string,
|
||||
options: CompilerOptions = {}
|
||||
): Promise<Program> {
|
||||
const buildCbs: any = [];
|
||||
const stateMaps = new Map<Symbol, Map<any, any>>();
|
||||
|
@ -63,7 +65,7 @@ export async function createProgram(
|
|||
let error = false;
|
||||
|
||||
const program: Program = {
|
||||
compilerOptions: options || {},
|
||||
compilerOptions: options,
|
||||
globalNamespace: createGlobalNamespace(),
|
||||
sourceFiles: [],
|
||||
literalTypes: new Map(),
|
||||
|
@ -94,7 +96,7 @@ export async function createProgram(
|
|||
await loadStandardLibrary(program);
|
||||
}
|
||||
|
||||
await loadMain(options);
|
||||
await loadMain(mainFile, options);
|
||||
|
||||
const checker = (program.checker = createChecker(program));
|
||||
program.checker.checkProgram(program);
|
||||
|
@ -187,36 +189,40 @@ export async function createProgram(
|
|||
}
|
||||
}
|
||||
|
||||
async function loadDirectory(rootDir: string) {
|
||||
const dir = await host.readDir(rootDir);
|
||||
for (const entry of dir) {
|
||||
if (entry.isFile()) {
|
||||
const path = join(rootDir, entry.name);
|
||||
if (entry.name.endsWith(".js")) {
|
||||
await loadJsFile(path);
|
||||
} else if (entry.name.endsWith(".adl")) {
|
||||
await loadAdlFile(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
async function loadDirectory(dir: string, diagnosticTarget?: DiagnosticTarget) {
|
||||
const pkgJsonPath = resolve(dir, "package.json");
|
||||
let [pkg] = await loadFile(host.readFile, pkgJsonPath, JSON.parse, program.reportDiagnostic, {
|
||||
allowFileNotFound: true,
|
||||
diagnosticTarget,
|
||||
});
|
||||
const mainFile = resolve(dir, pkg?.adlMain ?? "main.adl");
|
||||
await loadAdlFile(mainFile, diagnosticTarget);
|
||||
}
|
||||
|
||||
async function loadAdlFile(path: string) {
|
||||
async function loadAdlFile(path: string, diagnosticTarget?: DiagnosticTarget) {
|
||||
if (seenSourceFiles.has(path)) {
|
||||
return;
|
||||
}
|
||||
seenSourceFiles.add(path);
|
||||
|
||||
const contents = await host.readFile(path);
|
||||
if (!contents) {
|
||||
throw new Error("Couldn't load ADL file " + path);
|
||||
}
|
||||
const contents = await doIO(host.readFile, path, program.reportDiagnostic, {
|
||||
diagnosticTarget,
|
||||
});
|
||||
|
||||
await evalAdlScript(contents, path);
|
||||
if (contents) {
|
||||
await evalAdlScript(contents, path);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadJsFile(path: string) {
|
||||
const exports = await host.getJsImport(path);
|
||||
async function loadJsFile(path: string, diagnosticTarget: DiagnosticTarget) {
|
||||
const exports = await doIO(host.getJsImport, path, program.reportDiagnostic, {
|
||||
diagnosticTarget,
|
||||
jsDiagnosticTarget: { file: createSourceFile("", path), pos: 0, end: 0 },
|
||||
});
|
||||
|
||||
if (!exports) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const match of Object.keys(exports)) {
|
||||
// bind JS files early since this is the only work
|
||||
|
@ -279,12 +285,11 @@ export async function createProgram(
|
|||
const ext = extname(target);
|
||||
|
||||
if (ext === "") {
|
||||
// look for a main.adl
|
||||
await loadAdlFile(join(target, "main.adl"));
|
||||
} else if (ext === ".js") {
|
||||
await loadJsFile(target);
|
||||
await loadDirectory(target, stmt);
|
||||
} else if (ext === ".js" || ext === ".mjs") {
|
||||
await loadJsFile(target, stmt);
|
||||
} else if (ext === ".adl") {
|
||||
await loadAdlFile(target);
|
||||
await loadAdlFile(target, stmt);
|
||||
} else {
|
||||
program.reportDiagnostic(
|
||||
"Import paths must reference either a directory, a .adl file, or .js file",
|
||||
|
@ -342,7 +347,13 @@ export async function createProgram(
|
|||
host
|
||||
.realpath(path)
|
||||
.then((p) => cb(null, p))
|
||||
.catch((e) => cb(e));
|
||||
.catch((e) => {
|
||||
if (e.code === "ENOENT" || e.code === "ENOTDIR") {
|
||||
cb(null, path);
|
||||
} else {
|
||||
cb(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
packageFilter(pkg) {
|
||||
// this lets us follow node resolve semantics more-or-less exactly
|
||||
|
@ -355,8 +366,7 @@ export async function createProgram(
|
|||
if (err) {
|
||||
rejectP(err);
|
||||
} else if (!resolved) {
|
||||
// I don't know when this happens
|
||||
rejectP(new Error("Couldn't resolve module"));
|
||||
rejectP(new Error("BUG: Module resolution succeeded but didn't return a value."));
|
||||
} else {
|
||||
resolveP(resolved);
|
||||
}
|
||||
|
@ -365,14 +375,9 @@ export async function createProgram(
|
|||
});
|
||||
}
|
||||
|
||||
async function loadMain(options: CompilerOptions) {
|
||||
if (!options.mainFile) {
|
||||
throw new Error("Must specify a main file");
|
||||
}
|
||||
|
||||
const mainPath = resolve(host.getCwd(), options.mainFile);
|
||||
|
||||
const mainStat = await getMainPathStats(mainPath);
|
||||
async function loadMain(mainFile: string, options: CompilerOptions) {
|
||||
const mainPath = resolve(host.getCwd(), mainFile);
|
||||
const mainStat = await doIO(host.stat, mainPath, program.reportDiagnostic);
|
||||
if (!mainStat) {
|
||||
return;
|
||||
}
|
||||
|
@ -383,18 +388,6 @@ export async function createProgram(
|
|||
}
|
||||
}
|
||||
|
||||
async function getMainPathStats(mainPath: string) {
|
||||
try {
|
||||
return await host.stat(mainPath);
|
||||
} catch (e) {
|
||||
if (e.code === "ENOENT") {
|
||||
program.reportDiagnostic(Message.FileNotFound, NoTarget, [mainPath]);
|
||||
return undefined;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function getOption(key: string): string | undefined {
|
||||
return (options.miscOptions || {})[key];
|
||||
}
|
||||
|
@ -458,9 +451,9 @@ export async function createProgram(
|
|||
}
|
||||
|
||||
export async function compile(
|
||||
rootDir: string,
|
||||
mainFile: string,
|
||||
host: CompilerHost,
|
||||
options?: CompilerOptions
|
||||
): Promise<Program> {
|
||||
return await createProgram(host, { mainFile: rootDir, ...options });
|
||||
return await createProgram(host, mainFile, options);
|
||||
}
|
||||
|
|
|
@ -525,7 +525,7 @@ export interface Dirent {
|
|||
|
||||
export interface CompilerHost {
|
||||
// read a utf-8 encoded file
|
||||
readFile(path: string): Promise<string | undefined>;
|
||||
readFile(path: string): Promise<string>;
|
||||
|
||||
// read the contents of a directory
|
||||
readDir(path: string): Promise<Dirent[]>;
|
||||
|
|
|
@ -2,7 +2,15 @@ import fs from "fs";
|
|||
import { readdir, readFile, realpath, stat, writeFile } from "fs/promises";
|
||||
import { join, resolve } from "path";
|
||||
import { fileURLToPath, pathToFileURL, URL } from "url";
|
||||
import { CompilerHost } from "./types.js";
|
||||
import {
|
||||
createDiagnostic,
|
||||
createSourceFile,
|
||||
DiagnosticHandler,
|
||||
DiagnosticTarget,
|
||||
Message,
|
||||
NoTarget,
|
||||
} from "./diagnostics.js";
|
||||
import { CompilerHost, SourceFile } from "./types.js";
|
||||
|
||||
export const adlVersion = getVersion();
|
||||
|
||||
|
@ -40,6 +48,73 @@ export function deepClone<T>(value: T): T {
|
|||
return value;
|
||||
}
|
||||
|
||||
export interface FileHandlingOptions {
|
||||
allowFileNotFound?: boolean;
|
||||
diagnosticTarget?: DiagnosticTarget;
|
||||
jsDiagnosticTarget?: DiagnosticTarget;
|
||||
}
|
||||
|
||||
export async function doIO<T>(
|
||||
action: (path: string) => Promise<T>,
|
||||
path: string,
|
||||
reportDiagnostic: DiagnosticHandler,
|
||||
options?: FileHandlingOptions
|
||||
): Promise<T | undefined> {
|
||||
let result;
|
||||
try {
|
||||
result = await action(path);
|
||||
} catch (e) {
|
||||
let diagnostic;
|
||||
let target = options?.diagnosticTarget ?? NoTarget;
|
||||
|
||||
// blame the JS file, not the ADL import statement for JS syntax errors.
|
||||
if (e instanceof SyntaxError && options?.jsDiagnosticTarget) {
|
||||
target = options.jsDiagnosticTarget;
|
||||
}
|
||||
|
||||
switch (e.code) {
|
||||
case "ENOENT":
|
||||
if (options?.allowFileNotFound) {
|
||||
return undefined;
|
||||
}
|
||||
diagnostic = createDiagnostic(Message.FileNotFound, target, [path]);
|
||||
break;
|
||||
default:
|
||||
diagnostic = createDiagnostic(e.message, target);
|
||||
break;
|
||||
}
|
||||
|
||||
reportDiagnostic(diagnostic);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function loadFile<T>(
|
||||
read: (path: string) => Promise<string>,
|
||||
path: string,
|
||||
load: (contents: string) => T,
|
||||
reportDiagnostic: DiagnosticHandler,
|
||||
options?: FileHandlingOptions
|
||||
): Promise<[T | undefined, SourceFile]> {
|
||||
const contents = await doIO(read, path, reportDiagnostic, options);
|
||||
if (!contents) {
|
||||
return [undefined, createSourceFile("", path)];
|
||||
}
|
||||
|
||||
const file = createSourceFile(contents, path);
|
||||
let data: T;
|
||||
try {
|
||||
data = load(contents);
|
||||
} catch (e) {
|
||||
reportDiagnostic({ message: e.message, severity: "error", file });
|
||||
return [undefined, file];
|
||||
}
|
||||
|
||||
return [data, file];
|
||||
}
|
||||
|
||||
export const NodeHost: CompilerHost = {
|
||||
readFile: (path: string) => readFile(path, "utf-8"),
|
||||
readDir: (path: string) => readdir(path, { withFileTypes: true }),
|
||||
|
@ -49,7 +124,7 @@ export const NodeHost: CompilerHost = {
|
|||
getJsImport: (path: string) => import(pathToFileURL(path).href),
|
||||
getLibDirs() {
|
||||
const rootDir = this.getExecutionRoot();
|
||||
return [join(rootDir, "lib"), join(rootDir, "dist/lib")];
|
||||
return [join(rootDir, "lib")];
|
||||
},
|
||||
stat(path: string) {
|
||||
return stat(path);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { readFile } from "fs/promises";
|
||||
import { basename, extname, join } from "path";
|
||||
import { createDiagnostic, createSourceFile } from "../compiler/diagnostics.js";
|
||||
import { Message } from "../compiler/diagnostics.js";
|
||||
import { Diagnostic } from "../compiler/types.js";
|
||||
import { deepClone, deepFreeze } from "../compiler/util.js";
|
||||
import { deepClone, deepFreeze, loadFile } from "../compiler/util.js";
|
||||
import { ConfigValidator } from "./config-validator.js";
|
||||
import { ADLConfig } from "./types.js";
|
||||
|
||||
|
@ -24,14 +24,14 @@ const defaultConfig: ADLConfig = deepFreeze({
|
|||
export async function loadADLConfigInDir(directoryPath: string): Promise<ADLConfig> {
|
||||
for (const filename of configFilenames) {
|
||||
const filePath = join(directoryPath, filename);
|
||||
try {
|
||||
return await loadADLConfigFile(filePath);
|
||||
} catch (e) {
|
||||
if (e.code === "ENOENT") {
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
const config = await loadADLConfigFile(filePath);
|
||||
if (
|
||||
config.diagnostics.length === 1 &&
|
||||
config.diagnostics[0].code === Message.FileNotFound.code
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
return deepClone(defaultConfig);
|
||||
}
|
||||
|
@ -78,35 +78,32 @@ async function loadConfigFile(
|
|||
filePath: string,
|
||||
loadData: (content: string) => any
|
||||
): Promise<ADLConfig> {
|
||||
const content = await readFile(filePath, "utf-8");
|
||||
const file = createSourceFile(content, filePath);
|
||||
const diagnostics: Diagnostic[] = [];
|
||||
const reportDiagnostic = (d: Diagnostic) => diagnostics.push(d);
|
||||
|
||||
let loadDiagnostics: Diagnostic[];
|
||||
let data: any;
|
||||
try {
|
||||
data = loadData(content);
|
||||
loadDiagnostics = [];
|
||||
} catch (e) {
|
||||
loadDiagnostics = [createDiagnostic(e.message, { file, pos: 0, end: 0 })];
|
||||
let [data, file] = await loadFile(
|
||||
(path) => readFile(path, "utf-8"),
|
||||
filePath,
|
||||
loadData,
|
||||
reportDiagnostic
|
||||
);
|
||||
|
||||
if (data) {
|
||||
configValidator.validateConfig(data, file, reportDiagnostic);
|
||||
}
|
||||
|
||||
const validationDiagnostics = configValidator.validateConfig(data);
|
||||
const diagnostics = [...loadDiagnostics, ...validationDiagnostics];
|
||||
|
||||
if (diagnostics.some((d) => d.severity === "error")) {
|
||||
// NOTE: Don't trust the data if there are validation errors, and use
|
||||
// default config. Otherwise, we may return an object that does not
|
||||
// conform to ADLConfig's typing.
|
||||
data = defaultConfig;
|
||||
if (!data || diagnostics.length > 0) {
|
||||
// NOTE: Don't trust the data if there are errors and use default
|
||||
// config. Otherwise, we may return an object that does not conform to
|
||||
// ADLConfig's typing.
|
||||
data = deepClone(defaultConfig);
|
||||
} else {
|
||||
mergeDefaults(data, defaultConfig);
|
||||
}
|
||||
|
||||
return {
|
||||
...data,
|
||||
filename: filePath,
|
||||
diagnostics,
|
||||
};
|
||||
data.filename = filePath;
|
||||
data.diagnostics = diagnostics;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Ajv, { ErrorObject } from "ajv";
|
||||
import { compilerAssert } from "../compiler/diagnostics.js";
|
||||
import { compilerAssert, DiagnosticHandler } from "../compiler/diagnostics.js";
|
||||
import { Diagnostic, SourceFile } from "../compiler/types.js";
|
||||
import { ADLConfigJsonSchema } from "./config-schema.js";
|
||||
import { ADLRawConfig } from "./types.js";
|
||||
|
@ -15,20 +15,28 @@ export class ConfigValidator {
|
|||
* @param file @optional file for errors tracing.
|
||||
* @returns Validation
|
||||
*/
|
||||
public validateConfig(config: ADLRawConfig, file?: SourceFile): Diagnostic[] {
|
||||
public validateConfig(
|
||||
config: ADLRawConfig,
|
||||
file: SourceFile,
|
||||
reportDiagnostic: DiagnosticHandler
|
||||
): void {
|
||||
const validate = this.ajv.compile(ADLConfigJsonSchema);
|
||||
const valid = validate(config);
|
||||
compilerAssert(
|
||||
!valid || !validate.errors,
|
||||
"There should be errors reported if the config file is not valid."
|
||||
);
|
||||
return validate.errors?.map((e) => ajvErrorToDiagnostic(e, file)) ?? [];
|
||||
|
||||
for (const error of validate.errors ?? []) {
|
||||
const diagnostic = ajvErrorToDiagnostic(error, file);
|
||||
reportDiagnostic(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const IGNORED_AJV_PARAMS = new Set(["type", "errors"]);
|
||||
|
||||
function ajvErrorToDiagnostic(error: ErrorObject, file?: SourceFile): Diagnostic {
|
||||
function ajvErrorToDiagnostic(error: ErrorObject, file: SourceFile): Diagnostic {
|
||||
const messageLines = [`Schema violation: ${error.message} (${error.instancePath || "/"})`];
|
||||
for (const [name, value] of Object.entries(error.params).filter(
|
||||
([name]) => !IGNORED_AJV_PARAMS.has(name)
|
||||
|
@ -37,9 +45,6 @@ function ajvErrorToDiagnostic(error: ErrorObject, file?: SourceFile): Diagnostic
|
|||
messageLines.push(` ${name}: ${formattedValue}`);
|
||||
}
|
||||
|
||||
return {
|
||||
severity: "error",
|
||||
message: messageLines.join("\n"),
|
||||
...(file && { file }),
|
||||
};
|
||||
const message = messageLines.join("\n");
|
||||
return { message, severity: "error", file };
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
import "../dist/lib/decorators.js";
|
||||
import "./lib.adl";
|
|
@ -11,7 +11,7 @@ describe("adl: aliases", () => {
|
|||
|
||||
it("can alias a union expression", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
alias Foo = int32 | string;
|
||||
alias Bar = "hi" | 10;
|
||||
|
@ -37,7 +37,7 @@ describe("adl: aliases", () => {
|
|||
|
||||
it("can alias a deep union expression", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
alias Foo = int32 | string;
|
||||
alias Bar = "hi" | 10;
|
||||
|
@ -65,7 +65,7 @@ describe("adl: aliases", () => {
|
|||
|
||||
it("can alias a union expression with parameters", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
alias Foo<T> = int32 | T;
|
||||
|
||||
|
@ -88,7 +88,7 @@ describe("adl: aliases", () => {
|
|||
|
||||
it("can alias a deep union expression with parameters", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
alias Foo<T> = int32 | T;
|
||||
alias Bar<T, U> = Foo<T> | Foo<U>;
|
||||
|
@ -114,7 +114,7 @@ describe("adl: aliases", () => {
|
|||
|
||||
it("can alias an intersection expression", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
alias Foo = {a: string} & {b: string};
|
||||
alias Bar = {c: string} & {d: string};
|
||||
|
@ -140,7 +140,7 @@ describe("adl: aliases", () => {
|
|||
|
||||
it("can be used like any model", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
@test model Test { a: string };
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ describe("adl: semantic checks on source with parse errors", () => {
|
|||
|
||||
it("reports semantic errors in addition to parse errors", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`model M extends Q {
|
||||
a: B;
|
||||
a: C;
|
||||
|
|
|
@ -11,7 +11,7 @@ describe("adl: duplicate declarations", () => {
|
|||
|
||||
it("reports duplicate template parameters", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
model A<T, T> { }
|
||||
`
|
||||
|
@ -23,7 +23,7 @@ describe("adl: duplicate declarations", () => {
|
|||
|
||||
it("reports duplicate model declarations in global scope", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
model A { }
|
||||
model A { }
|
||||
|
@ -36,7 +36,7 @@ describe("adl: duplicate declarations", () => {
|
|||
|
||||
it("reports duplicate model declarations in a single namespace", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
namespace Foo;
|
||||
model A { }
|
||||
|
@ -50,7 +50,7 @@ describe("adl: duplicate declarations", () => {
|
|||
|
||||
it("reports duplicate model declarations across multiple namespaces", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
namespace N {
|
||||
model A { };
|
||||
|
@ -67,6 +67,13 @@ describe("adl: duplicate declarations", () => {
|
|||
});
|
||||
|
||||
it("reports duplicate model declarations across multiple files and namespaces", async () => {
|
||||
testHost.addAdlFile(
|
||||
"main.adl",
|
||||
`
|
||||
import "./a.adl";
|
||||
import "./b.adl";
|
||||
`
|
||||
);
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
`
|
||||
|
|
|
@ -11,7 +11,7 @@ describe("adl: enums", () => {
|
|||
|
||||
it("can be valueless", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
@test enum E {
|
||||
A, B, C
|
||||
|
@ -31,7 +31,7 @@ describe("adl: enums", () => {
|
|||
|
||||
it("can have values", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
@test enum E {
|
||||
@test("A") A: "a";
|
||||
|
@ -56,7 +56,7 @@ describe("adl: enums", () => {
|
|||
|
||||
it("can be a model property", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
namespace Foo;
|
||||
enum E { A, B, C }
|
||||
|
|
|
@ -10,7 +10,7 @@ describe("adl: loader", () => {
|
|||
it("loads ADL and JS files", async () => {
|
||||
testHost.addJsFile("blue.js", { blue() {} });
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
import "./b.adl";
|
||||
import "./blue.js";
|
||||
|
@ -39,6 +39,6 @@ describe("adl: loader", () => {
|
|||
`
|
||||
);
|
||||
|
||||
await testHost.compile("a.adl");
|
||||
await testHost.compile("main.adl");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,8 +17,9 @@ describe("adl: namespaces with blocks", () => {
|
|||
|
||||
it("can be decorated", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
import "./blue.js";
|
||||
@blue @test namespace Z.Q;
|
||||
@blue @test namespace N { }
|
||||
@blue @test namespace X.Y { }
|
||||
|
@ -37,7 +38,7 @@ describe("adl: namespaces with blocks", () => {
|
|||
|
||||
it("merges like namespaces", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
@test
|
||||
namespace N { @test model X { x: string } }
|
||||
|
@ -58,6 +59,14 @@ describe("adl: namespaces with blocks", () => {
|
|||
});
|
||||
|
||||
it("merges like namespaces across files", async () => {
|
||||
testHost.addAdlFile(
|
||||
"main.adl",
|
||||
`
|
||||
import "./a.adl";
|
||||
import "./b.adl";
|
||||
import "./c.adl";
|
||||
`
|
||||
);
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
`
|
||||
|
@ -90,6 +99,14 @@ describe("adl: namespaces with blocks", () => {
|
|||
});
|
||||
|
||||
it("merges sub-namespaces across files", async () => {
|
||||
testHost.addAdlFile(
|
||||
"main.adl",
|
||||
`
|
||||
import "./a.adl";
|
||||
import "./b.adl";
|
||||
import "./c.adl";
|
||||
`
|
||||
);
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
`
|
||||
|
@ -117,7 +134,7 @@ describe("adl: namespaces with blocks", () => {
|
|||
|
||||
it("can see things in outer scope same file", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
model A { }
|
||||
namespace N { model B extends A { } }
|
||||
|
@ -127,6 +144,14 @@ describe("adl: namespaces with blocks", () => {
|
|||
});
|
||||
|
||||
it("can see things in outer scope cross file", async () => {
|
||||
testHost.addAdlFile(
|
||||
"main.adl",
|
||||
`
|
||||
import "./a.adl";
|
||||
import "./b.adl";
|
||||
import "./c.adl";
|
||||
`
|
||||
);
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
`
|
||||
|
@ -153,7 +178,7 @@ describe("adl: namespaces with blocks", () => {
|
|||
|
||||
it("accumulates declarations inside of it", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
@test namespace Foo {
|
||||
namespace Bar { };
|
||||
|
@ -163,7 +188,7 @@ describe("adl: namespaces with blocks", () => {
|
|||
`
|
||||
);
|
||||
|
||||
const { Foo } = (await testHost.compile("/a.adl")) as {
|
||||
const { Foo } = (await testHost.compile("./")) as {
|
||||
Foo: NamespaceType;
|
||||
};
|
||||
|
||||
|
@ -187,6 +212,14 @@ describe("adl: blockless namespaces", () => {
|
|||
});
|
||||
|
||||
it("merges properly with other namespaces", async () => {
|
||||
testHost.addAdlFile(
|
||||
"main.adl",
|
||||
`
|
||||
import "./a.adl";
|
||||
import "./b.adl";
|
||||
import "./c.adl";
|
||||
`
|
||||
);
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
`
|
||||
|
@ -215,7 +248,7 @@ describe("adl: blockless namespaces", () => {
|
|||
|
||||
it("does lookup correctly", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
namespace Repro;
|
||||
model Yo {
|
||||
|
@ -235,7 +268,7 @@ describe("adl: blockless namespaces", () => {
|
|||
|
||||
it("does lookup correctly with nested namespaces", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
namespace Repro;
|
||||
model Yo {
|
||||
|
@ -265,7 +298,7 @@ describe("adl: blockless namespaces", () => {
|
|||
|
||||
it("binds correctly", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
namespace N.M;
|
||||
model A { }
|
||||
|
@ -287,7 +320,7 @@ describe("adl: blockless namespaces", () => {
|
|||
|
||||
it("works with blockful namespaces", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
@test
|
||||
namespace N;
|
||||
|
@ -314,6 +347,13 @@ describe("adl: blockless namespaces", () => {
|
|||
});
|
||||
|
||||
it("works with nested blockless and blockfull namespaces", async () => {
|
||||
testHost.addAdlFile(
|
||||
"main.adl",
|
||||
`
|
||||
import "./a.adl";
|
||||
import "./b.adl";
|
||||
`
|
||||
);
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
`
|
||||
|
|
|
@ -17,8 +17,9 @@ describe("adl: spread", () => {
|
|||
|
||||
it("clones decorated properties", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
import "./blue.js";
|
||||
model A { @blue foo: string }
|
||||
model B { @blue bar: string }
|
||||
@test model C { ... A, ... B }
|
||||
|
|
|
@ -10,6 +10,13 @@ describe("adl: using statements", () => {
|
|||
});
|
||||
|
||||
it("works in global scope", async () => {
|
||||
testHost.addAdlFile(
|
||||
"main.adl",
|
||||
`
|
||||
import "./a.adl";
|
||||
import "./b.adl";
|
||||
`
|
||||
);
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
`
|
||||
|
@ -33,6 +40,13 @@ describe("adl: using statements", () => {
|
|||
});
|
||||
|
||||
it("works in namespaces", async () => {
|
||||
testHost.addAdlFile(
|
||||
"main.adl",
|
||||
`
|
||||
import "./a.adl";
|
||||
import "./b.adl";
|
||||
`
|
||||
);
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
`
|
||||
|
@ -57,6 +71,13 @@ describe("adl: using statements", () => {
|
|||
});
|
||||
|
||||
it("works with dotted namespaces", async () => {
|
||||
testHost.addAdlFile(
|
||||
"main.adl",
|
||||
`
|
||||
import "./a.adl";
|
||||
import "./b.adl";
|
||||
`
|
||||
);
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
`
|
||||
|
@ -80,6 +101,13 @@ describe("adl: using statements", () => {
|
|||
});
|
||||
|
||||
it("throws errors for duplicate imported usings", async () => {
|
||||
testHost.addAdlFile(
|
||||
"main.adl",
|
||||
`
|
||||
import "./a.adl";
|
||||
import "./b.adl";
|
||||
`
|
||||
);
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
`
|
||||
|
@ -101,6 +129,13 @@ describe("adl: using statements", () => {
|
|||
});
|
||||
|
||||
it("throws errors for different usings with the same bindings", async () => {
|
||||
testHost.addAdlFile(
|
||||
"main.adl",
|
||||
`
|
||||
import "./a.adl";
|
||||
import "./b.adl";
|
||||
`
|
||||
);
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
`
|
||||
|
@ -126,6 +161,13 @@ describe("adl: using statements", () => {
|
|||
});
|
||||
|
||||
it("resolves 'local' decls over usings", async () => {
|
||||
testHost.addAdlFile(
|
||||
"main.adl",
|
||||
`
|
||||
import "./a.adl";
|
||||
import "./b.adl";
|
||||
`
|
||||
);
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
`
|
||||
|
@ -151,6 +193,13 @@ describe("adl: using statements", () => {
|
|||
});
|
||||
|
||||
it("usings are local to a file", async () => {
|
||||
testHost.addAdlFile(
|
||||
"main.adl",
|
||||
`
|
||||
import "./a.adl";
|
||||
import "./b.adl";
|
||||
`
|
||||
);
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
`
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { deepStrictEqual } from "assert";
|
||||
import { dirname, join, resolve } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { createSourceFile } from "../../compiler/diagnostics.js";
|
||||
import { Diagnostic } from "../../compiler/types.js";
|
||||
import { ConfigValidator } from "../../config/config-validator.js";
|
||||
import { loadADLConfigInDir } from "../../config/index.js";
|
||||
import { ADLRawConfig, loadADLConfigInDir } from "../../config/index.js";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
|
@ -107,10 +109,18 @@ describe("adl: config file loading", () => {
|
|||
|
||||
describe("validation", () => {
|
||||
const validator = new ConfigValidator();
|
||||
const file = createSourceFile("<content>", "<path>");
|
||||
|
||||
function validate(data: ADLRawConfig) {
|
||||
const diagnostics: Diagnostic[] = [];
|
||||
validator.validateConfig(data, file, (d) => diagnostics.push(d));
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
it("does not allow additional properties", () => {
|
||||
deepStrictEqual(validator.validateConfig({ someCustomProp: true } as any), [
|
||||
deepStrictEqual(validate({ someCustomProp: true } as any), [
|
||||
{
|
||||
file,
|
||||
severity: "error",
|
||||
message:
|
||||
"Schema violation: must NOT have additional properties (/)\n additionalProperty: someCustomProp",
|
||||
|
@ -119,16 +129,17 @@ describe("adl: config file loading", () => {
|
|||
});
|
||||
|
||||
it("fails if passing the wrong type", () => {
|
||||
deepStrictEqual(validator.validateConfig({ emitters: true } as any), [
|
||||
deepStrictEqual(validate({ emitters: true } as any), [
|
||||
{
|
||||
file,
|
||||
severity: "error",
|
||||
message: "Schema violation: must be object (/emitters)",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("succeeeds if config is valid", () => {
|
||||
deepStrictEqual(validator.validateConfig({ lint: { rules: { foo: "on" } } }), []);
|
||||
it("succeeds if config is valid", () => {
|
||||
deepStrictEqual(validate({ lint: { rules: { foo: "on" } } }), []);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ describe("adl: range limiting decorators", () => {
|
|||
|
||||
it("applies @minimum and @maximum decorators", async () => {
|
||||
testHost.addAdlFile(
|
||||
"a.adl",
|
||||
"main.adl",
|
||||
`
|
||||
@test model A { @minValue(15) foo: int32; @maxValue(55) boo: float32; }
|
||||
@test model B { @maxValue(20) bar: int64; @minValue(23) car: float64; }
|
||||
|
|
|
@ -12,10 +12,7 @@ describe("adl: libraries", () => {
|
|||
const mainFile = fileURLToPath(
|
||||
new URL(`../../../test/libraries/${lib}/main.adl`, import.meta.url)
|
||||
);
|
||||
await createProgram(NodeHost, {
|
||||
mainFile,
|
||||
noEmit: true,
|
||||
});
|
||||
await createProgram(NodeHost, mainFile, { noEmit: true });
|
||||
} catch (e) {
|
||||
console.error(e.diagnostics);
|
||||
throw e;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { readdir, readFile } from "fs/promises";
|
||||
import { basename, isAbsolute, join, normalize, relative, resolve, sep } from "path";
|
||||
import { basename, extname, isAbsolute, relative, resolve, sep } from "path";
|
||||
import { fileURLToPath, pathToFileURL } from "url";
|
||||
import { formatDiagnostic, logDiagnostics, logVerboseTestOutput } from "../compiler/diagnostics.js";
|
||||
import { CompilerOptions } from "../compiler/options.js";
|
||||
|
@ -22,23 +22,33 @@ export interface TestHost {
|
|||
/**
|
||||
* Virtual filesystem used in the tests.
|
||||
*/
|
||||
fs: { [name: string]: string };
|
||||
fs: Map<string, string>;
|
||||
}
|
||||
|
||||
class TestHostError extends Error {
|
||||
constructor(message: string, public code: "ENOENT" | "ERR_MODULE_NOT_FOUND") {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function createTestHost(): Promise<TestHost> {
|
||||
const testTypes: Record<string, Type> = {};
|
||||
let program: Program = undefined as any; // in practice it will always be initialized
|
||||
const virtualFs: { [name: string]: string } = {};
|
||||
const jsImports: { [path: string]: Promise<any> } = {};
|
||||
const virtualFs = new Map<string, string>();
|
||||
const jsImports = new Map<string, Promise<any>>();
|
||||
const compilerHost: CompilerHost = {
|
||||
async readFile(path: string) {
|
||||
return virtualFs[path];
|
||||
const contents = virtualFs.get(path);
|
||||
if (contents === undefined) {
|
||||
throw new TestHostError(`File ${path} not found.`, "ENOENT");
|
||||
}
|
||||
return contents;
|
||||
},
|
||||
|
||||
async readDir(path: string) {
|
||||
const contents = [];
|
||||
|
||||
for (const fsPath of Object.keys(virtualFs)) {
|
||||
for (const fsPath of virtualFs.keys()) {
|
||||
if (isContainedIn(path, fsPath)) {
|
||||
contents.push({
|
||||
isFile() {
|
||||
|
@ -56,7 +66,7 @@ export async function createTestHost(): Promise<TestHost> {
|
|||
},
|
||||
|
||||
async writeFile(path: string, content: string) {
|
||||
virtualFs[path] = content;
|
||||
virtualFs.set(path, content);
|
||||
},
|
||||
|
||||
getLibDirs() {
|
||||
|
@ -68,7 +78,11 @@ export async function createTestHost(): Promise<TestHost> {
|
|||
},
|
||||
|
||||
getJsImport(path) {
|
||||
return jsImports[path];
|
||||
const module = jsImports.get(path);
|
||||
if (module === undefined) {
|
||||
throw new TestHostError(`Module ${path} not found`, "ERR_MODULE_NOT_FOUND");
|
||||
}
|
||||
return module;
|
||||
},
|
||||
|
||||
getCwd() {
|
||||
|
@ -76,7 +90,7 @@ export async function createTestHost(): Promise<TestHost> {
|
|||
},
|
||||
|
||||
async stat(path: string) {
|
||||
if (virtualFs.hasOwnProperty(path)) {
|
||||
if (virtualFs.has(path)) {
|
||||
return {
|
||||
isDirectory() {
|
||||
return false;
|
||||
|
@ -87,7 +101,7 @@ export async function createTestHost(): Promise<TestHost> {
|
|||
};
|
||||
}
|
||||
|
||||
for (const fsPath of Object.keys(virtualFs)) {
|
||||
for (const fsPath of virtualFs.keys()) {
|
||||
if (fsPath.startsWith(path) && fsPath !== path) {
|
||||
return {
|
||||
isDirectory() {
|
||||
|
@ -100,7 +114,7 @@ export async function createTestHost(): Promise<TestHost> {
|
|||
}
|
||||
}
|
||||
|
||||
throw { code: "ENOENT" };
|
||||
throw new TestHostError(`File ${path} not found`, "ENOENT");
|
||||
},
|
||||
|
||||
// symlinks not supported in test-host
|
||||
|
@ -110,27 +124,33 @@ export async function createTestHost(): Promise<TestHost> {
|
|||
};
|
||||
|
||||
// load standard library into the vfs
|
||||
for (const relDir of ["../../lib", "../../../lib"]) {
|
||||
for (const [relDir, virtualDir] of [
|
||||
["../../lib", "/.adl/dist/lib"],
|
||||
["../../../lib", "/.adl/lib"],
|
||||
]) {
|
||||
const dir = resolve(fileURLToPath(import.meta.url), relDir);
|
||||
const contents = await readdir(dir, { withFileTypes: true });
|
||||
for (const entry of contents) {
|
||||
const entries = await readdir(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const realPath = resolve(dir, entry.name);
|
||||
const virtualPath = resolve(virtualDir, entry.name);
|
||||
if (entry.isFile()) {
|
||||
const path = join(dir, entry.name);
|
||||
const virtualDir = compilerHost.getLibDirs()[0];
|
||||
const key = normalize(join(virtualDir, entry.name));
|
||||
|
||||
if (entry.name.endsWith(".js")) {
|
||||
jsImports[key] = import(pathToFileURL(path).href);
|
||||
virtualFs[key] = ""; // don't need contents.
|
||||
} else {
|
||||
const contents = await readFile(path, "utf-8");
|
||||
virtualFs[key] = contents;
|
||||
switch (extname(entry.name)) {
|
||||
case ".adl":
|
||||
const contents = await readFile(realPath, "utf-8");
|
||||
virtualFs.set(virtualPath, contents);
|
||||
break;
|
||||
case ".js":
|
||||
case ".mjs":
|
||||
jsImports.set(virtualPath, import(pathToFileURL(realPath).href));
|
||||
virtualFs.set(virtualPath, ""); // don't need contents.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add test decorators
|
||||
addAdlFile("/.adl/test-lib/main.adl", 'import "./test.js";');
|
||||
addJsFile("/.adl/test-lib/test.js", {
|
||||
test(_: any, target: Type, name?: string) {
|
||||
if (!name) {
|
||||
|
@ -161,26 +181,25 @@ export async function createTestHost(): Promise<TestHost> {
|
|||
};
|
||||
|
||||
function addAdlFile(path: string, contents: string) {
|
||||
virtualFs[resolve(compilerHost.getCwd(), path)] = contents;
|
||||
virtualFs.set(resolve(compilerHost.getCwd(), path), contents);
|
||||
}
|
||||
|
||||
function addJsFile(path: string, contents: any) {
|
||||
const key = resolve(compilerHost.getCwd(), path);
|
||||
// don't need file contents;
|
||||
virtualFs[key] = "";
|
||||
jsImports[key] = new Promise((r) => r(contents));
|
||||
virtualFs.set(key, ""); // don't need contents
|
||||
jsImports.set(key, new Promise((r) => r(contents)));
|
||||
}
|
||||
|
||||
async function addRealAdlFile(path: string, existingPath: string) {
|
||||
virtualFs[resolve(compilerHost.getCwd(), path)] = await readFile(existingPath, "utf8");
|
||||
virtualFs.set(resolve(compilerHost.getCwd(), path), await readFile(existingPath, "utf8"));
|
||||
}
|
||||
|
||||
async function addRealJsFile(path: string, existingPath: string) {
|
||||
const key = resolve(compilerHost.getCwd(), path);
|
||||
const exports = await import(pathToFileURL(existingPath).href);
|
||||
|
||||
virtualFs[key] = "";
|
||||
jsImports[key] = exports;
|
||||
virtualFs.set(key, "");
|
||||
jsImports.set(key, exports);
|
||||
}
|
||||
|
||||
async function compile(main: string, options: CompilerOptions = {}) {
|
||||
|
@ -198,18 +217,15 @@ export async function createTestHost(): Promise<TestHost> {
|
|||
}
|
||||
|
||||
async function compileAndDiagnose(
|
||||
main: string,
|
||||
mainFile: string,
|
||||
options: CompilerOptions = {}
|
||||
): Promise<[Record<string, Type>, readonly Diagnostic[]]> {
|
||||
// default is noEmit
|
||||
if (!options.hasOwnProperty("noEmit")) {
|
||||
options.noEmit = true;
|
||||
if (options.noEmit === undefined) {
|
||||
// default for tests is noEmit
|
||||
options = { ...options, noEmit: true };
|
||||
}
|
||||
|
||||
program = await createProgram(compilerHost, {
|
||||
mainFile: main,
|
||||
...options,
|
||||
});
|
||||
program = await createProgram(compilerHost, mainFile, options);
|
||||
logVerboseTestOutput((log) => logDiagnostics(program.diagnostics, log));
|
||||
return [testTypes, program.diagnostics];
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче