Skip to content

Commit

Permalink
Show errors in the right place
Browse files Browse the repository at this point in the history
  • Loading branch information
timotheeguerin committed Nov 29, 2023
1 parent eca2fb4 commit d2c4c06
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 19 deletions.
3 changes: 2 additions & 1 deletion packages/compiler/src/config/config-to-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ export function resolveOptionsFromConfig(config: TypeSpecConfig, options: Config

const resolvedOptions: CompilerOptions = omitUndefined({
outputDir: expandedConfig.outputDir,
config: config.filename,
configPath: config.filename,
config: config,
additionalImports: expandedConfig["imports"],
warningAsError: expandedConfig.warnAsError,
trace: expandedConfig.trace,
Expand Down
9 changes: 6 additions & 3 deletions packages/compiler/src/core/options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EmitterOptions } from "../config/types.js";
import { EmitterOptions, TypeSpecConfig } from "../config/types.js";
import { LinterRuleSet, ParseOptions } from "./types.js";

export interface CompilerOptions {
Expand All @@ -11,9 +11,9 @@ export interface CompilerOptions {
outputDir?: string;

/**
* Path to config YAML file or folder in which to search for default tspconfig.yaml file.
* Path to config YAML file used, this is also where the project root should be.
*/
config?: string;
configPath?: string;

/**
* @deprecated use outputDir.
Expand Down Expand Up @@ -63,4 +63,7 @@ export interface CompilerOptions {

/** Ruleset to enable for linting. */
linterRuleSet?: LinterRuleSet;

/** @internal */
config?: TypeSpecConfig;
}
10 changes: 8 additions & 2 deletions packages/compiler/src/core/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ export async function compile(
getGlobalNamespaceType,
resolveTypeReference,
getSourceFileLocationContext,
projectRoot: getDirectoryPath(options.config ?? resolvedMain ?? ""),
projectRoot: getDirectoryPath(options.configPath ?? resolvedMain ?? ""),
};

trace("compiler.options", JSON.stringify(options, null, 2));
Expand Down Expand Up @@ -753,7 +753,13 @@ export async function compile(
if (libDefinition?.emitter?.options) {
const diagnostics = libDefinition?.emitterOptionValidator?.validate(
emitterOptions,
NoTarget
options.config?.file
? {
kind: "path-target",
path: ["options", emitterNameOrPath],
script: options.config.file,
}
: NoTarget
);
if (diagnostics && diagnostics.length > 0) {
program.reportDiagnostics(diagnostics);
Expand Down
44 changes: 34 additions & 10 deletions packages/compiler/src/core/schema-validator.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import Ajv, { ErrorObject, Options } from "ajv";
import { getLocationInYamlScript } from "../yaml/diagnostics.js";
import { YamlScript } from "../yaml/types.js";
import { YamlPathTarget, YamlScript } from "../yaml/types.js";
import { compilerAssert } from "./diagnostics.js";
import { createDiagnostic } from "./messages.js";
import { isPathAbsolute } from "./path-utils.js";
import { Diagnostic, JSONSchemaType, JSONSchemaValidator, NoTarget, SourceFile } from "./types.js";
import {
Diagnostic,
DiagnosticTarget,
JSONSchemaType,
JSONSchemaValidator,
NoTarget,
SourceFile,
} from "./types.js";

export interface JSONSchemaValidatorOptions {
coerceTypes?: boolean;
Expand All @@ -31,7 +38,7 @@ export function createJSONSchemaValidator<T>(

function validate(
config: unknown,
target: YamlScript | SourceFile | typeof NoTarget
target: YamlScript | YamlPathTarget | SourceFile | typeof NoTarget
): Diagnostic[] {
const validate = ajv.compile(schema);
const valid = validate(config);
Expand All @@ -55,14 +62,9 @@ const IGNORED_AJV_PARAMS = new Set(["type", "errors"]);
function ajvErrorToDiagnostic(
obj: unknown,
error: ErrorObject<string, Record<string, any>, unknown>,
target: YamlScript | SourceFile | typeof NoTarget
target: YamlScript | YamlPathTarget | SourceFile | typeof NoTarget
): Diagnostic {
const tspTarget =
target === NoTarget
? target
: "kind" in target
? getLocationInYamlScript(target, getErrorPath(error), "key")
: { file: target, pos: 0, end: 0 };
const tspTarget = resolveTarget(error, target);
if (error.params.format === "absolute-path") {
return createDiagnostic({
code: "config-path-absolute",
Expand All @@ -88,6 +90,28 @@ function ajvErrorToDiagnostic(
};
}

function resolveTarget(
error: ErrorObject<string, Record<string, any>, unknown>,
target: YamlScript | YamlPathTarget | SourceFile | typeof NoTarget
): DiagnosticTarget | typeof NoTarget {
if (target === NoTarget) {
return NoTarget;
}
if (!("kind" in target)) {
return { file: target, pos: 0, end: 0 };
}
switch (target.kind) {
case "yaml-script":
return getLocationInYamlScript(target, getErrorPath(error), "key");
case "path-target":
return getLocationInYamlScript(
target.script,
[...target.path, ...getErrorPath(error)],
"key"
);
}
}

function getErrorPath(error: ErrorObject<string, Record<string, any>, unknown>): string[] {
const instancePath = parseJsonPointer(error.instancePath);
switch (error.keyword) {
Expand Down
11 changes: 9 additions & 2 deletions packages/compiler/src/core/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { JSONSchemaType as AjvJSONSchemaType } from "ajv";
import { TypeEmitter } from "../emitter-framework/type-emitter.js";
import { AssetEmitter } from "../emitter-framework/types.js";
import { YamlScript } from "../yaml/types.js";
import { YamlPathTarget, YamlScript } from "../yaml/types.js";
import { ModuleResolutionResult } from "./module-resolver.js";
import { Program } from "./program.js";

Expand Down Expand Up @@ -1963,6 +1963,9 @@ export type TypeOfDiagnostics<T extends DiagnosticMap<any>> = T extends Diagnost

export type JSONSchemaType<T> = AjvJSONSchemaType<T>;

/**
* @internal
*/
export interface JSONSchemaValidator {
/**
* Validate the configuration against its JSON Schema.
Expand All @@ -1971,7 +1974,10 @@ export interface JSONSchemaValidator {
* @param target Source file target to use for diagnostics.
* @returns Diagnostics produced by schema validation of the configuration.
*/
validate(config: unknown, target: YamlScript | SourceFile | typeof NoTarget): Diagnostic[];
validate(
config: unknown,
target: YamlScript | YamlPathTarget | SourceFile | typeof NoTarget
): Diagnostic[];
}

/** @deprecated Use TypeSpecLibraryDef */
Expand Down Expand Up @@ -2092,6 +2098,7 @@ export interface TypeSpecLibrary<
> extends TypeSpecLibraryDef<T, E> {
/**
* JSON Schema validator for emitter options
* @internal
*/
readonly emitterOptionValidator?: JSONSchemaValidator;

Expand Down
8 changes: 8 additions & 0 deletions packages/compiler/src/yaml/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,12 @@ export interface YamlScript {
readonly doc: Document.Parsed;
}

/**
* Represent the location of a value in a yaml script.
*/
export interface YamlPathTarget {
kind: "path-target";
script: YamlScript;
path: string[];
}
export type YamlDiagnosticTargetType = "value" | "key";
2 changes: 1 addition & 1 deletion packages/samples/src/sample-snapshot-testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ function defineSampleSnaphotTest(
const emit = options.emit;
if (emit === undefined || emit.length === 0) {
fail(
`No emitters configured for sample "${sample.name}". Make sure the config at: "${options.config}" is correct.`
`No emitters configured for sample "${sample.name}". Make sure the config at: "${options.configPath}" is correct.`
);
}

Expand Down

0 comments on commit d2c4c06

Please sign in to comment.