UNPKG

@typespec/compiler

Version:

TypeSpec Compiler Preview

178 lines 6.8 kB
import { createDiagnostic } from "../core/messages.js"; import { getDirectoryPath, isPathAbsolute, joinPaths, resolvePath } from "../core/path-utils.js"; import { createJSONSchemaValidator } from "../core/schema-validator.js"; import { createSourceFile } from "../core/source-file.js"; import { NoTarget } from "../core/types.js"; import { doIO } from "../utils/io.js"; import { deepClone, deepFreeze, omitUndefined } from "../utils/misc.js"; import { getLocationInYamlScript } from "../yaml/index.js"; import { parseYaml } from "../yaml/parser.js"; import { TypeSpecConfigJsonSchema } from "./config-schema.js"; export const TypeSpecConfigFilename = "tspconfig.yaml"; export const defaultConfig = deepFreeze({ outputDir: "{cwd}/tsp-output", diagnostics: [], }); /** * Look for the project root by looking up until a `tspconfig.yaml` is found. * @param path Path to the file or the folder to start looking */ export async function findTypeSpecConfigPath(host, path, lookup = true) { // if the path is a file, return immediately const stats = await doIO(() => host.stat(path), path, () => { }, { allowFileNotFound: true }); if (!stats) { return undefined; } else if (stats.isFile()) { return path; } let current = path; // only recurse if the path is a directory and the flag was set to true (only for default case) // otherwise, look in the specific directory for tspconfig.yaml ONLY if (!lookup) { current = `${path}/tspconfig.yaml`; const stats = await doIO(() => host.stat(current), current, () => { }, { allowFileNotFound: true }); if (stats?.isFile()) { return current; } return undefined; } else { while (true) { const pkgPath = await searchConfigFile(host, current, TypeSpecConfigFilename); // if found either file in current folder, return it if (pkgPath !== undefined) { return pkgPath; } const parent = getDirectoryPath(current); if (parent === current) { return undefined; } current = parent; } } } /** * Load the TypeSpec configuration for the provided path or directory * @param host * @param path */ export async function loadTypeSpecConfigForPath(host, path, errorIfNotFound = false, lookup = true) { const typespecConfigPath = await findTypeSpecConfigPath(host, path, lookup); if (typespecConfigPath === undefined) { const projectRoot = getDirectoryPath(path); const tsConfig = { ...deepClone(defaultConfig), projectRoot: projectRoot }; if (errorIfNotFound) { tsConfig.diagnostics.push(createDiagnostic({ code: "config-path-not-found", format: { path: path, }, target: NoTarget, })); } return tsConfig; } const tsConfig = await loadTypeSpecConfigFile(host, typespecConfigPath); return tsConfig; } /** * Load given file as a TypeSpec configuration */ export async function loadTypeSpecConfigFile(host, filePath) { const config = await loadConfigFile(host, filePath, parseYaml); if (config.diagnostics.length === 0 && config.extends) { const extendPath = resolvePath(getDirectoryPath(filePath), config.extends); const parent = await loadTypeSpecConfigFile(host, extendPath); if (parent.diagnostics.length > 0) { return { ...config, diagnostics: parent.diagnostics, }; } return { ...parent, ...config, }; } return { ...deepClone(defaultConfig), ...config, }; } const configValidator = createJSONSchemaValidator(TypeSpecConfigJsonSchema); async function searchConfigFile(host, path, filename) { const pkgPath = joinPaths(path, filename); const stat = await doIO(() => host.stat(pkgPath), pkgPath, () => { }); return stat?.isFile() === true ? pkgPath : undefined; } async function loadConfigFile(host, filename, loadData) { let diagnostics = []; const reportDiagnostic = (d) => diagnostics.push(d); const file = (await doIO(host.readFile, filename, reportDiagnostic)) ?? createSourceFile("", filename); const [yamlScript, yamlDiagnostics] = loadData(file); yamlDiagnostics.forEach((d) => reportDiagnostic(d)); let data = yamlScript.value; if (data) { diagnostics = diagnostics.concat(configValidator.validate(data, yamlScript)); } 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 // TypeSpecConfig's typing. data = deepClone(defaultConfig); } const emit = data.emit; const options = data.options; return omitUndefined({ projectRoot: getDirectoryPath(filename), file: yamlScript, filename, diagnostics, extends: data.extends, environmentVariables: data["environment-variables"], parameters: data.parameters, outputDir: data["output-dir"] ?? "{cwd}/tsp-output", warnAsError: data["warn-as-error"], imports: data.imports, trace: typeof data.trace === "string" ? [data.trace] : data.trace, emit, options, linter: data.linter, }); } export function validateConfigPathsAbsolute(config) { const diagnostics = []; function checkPath(value, path) { if (value === undefined) { return; } const diagnostic = validatePathAbsolute(value, config.file ? { file: config.file, path } : NoTarget); if (diagnostic) { diagnostics.push(diagnostic); } } checkPath(config.outputDir, ["output-dir"]); for (const [emitterName, emitterOptions] of Object.entries(config.options ?? {})) { checkPath(emitterOptions["emitter-output-dir"], ["options", emitterName, "emitter-output-dir"]); } return diagnostics; } function validatePathAbsolute(path, target) { if (path.startsWith(".") || !isPathAbsolute(path)) { return createDiagnostic({ code: "config-path-absolute", format: { path }, target: target === NoTarget ? target : getLocationInYamlScript(target.file, target.path), }); } // if (path.includes("\\")) { // return createDiagnostic({ // code: "path-unix-style", // format: { path }, // target: target === NoTarget ? target : getLocationInYamlScript(target.file, target.path), // }); // } return undefined; } //# sourceMappingURL=config-loader.js.map