Better errors when an image is missing

This commit is contained in:
pietervdvn 2022-07-06 11:14:19 +02:00
parent 494bc28ddf
commit f27b60f80d
3 changed files with 65 additions and 23 deletions

View file

@ -39,6 +39,14 @@ export abstract class Conversion<TIn, TOut> {
return DesugaringStep.strict(fixed)
}
public convertJoin(json: TIn, context: string, errors: string[], warnings?: string[], information?: string[]): TOut {
const fixed = this.convert(json, context)
errors?.push(...(fixed.errors ?? []))
warnings?.push(...(fixed.warnings ?? []))
information?.push(...(fixed.information ?? []))
return fixed.result
}
public andThenF<X>(f: (tout:TOut) => X ): Conversion<TIn, X>{
return new Pipe(
this,

View file

@ -45,6 +45,53 @@ class ValidateLanguageCompleteness extends DesugaringStep<any> {
}
}
export class DoesImageExist extends DesugaringStep<string>{
private readonly _knownImagePaths: Set<string>;
public static doesPathExist : (path: string) => boolean = undefined;
constructor(knownImagePaths: Set<string>) {
super("Checks if an image exists", [], "DoesImageExist");
this._knownImagePaths = knownImagePaths;
}
convert(image: string, context: string): { result: string; errors?: string[]; warnings?: string[]; information?: string[] } {
const errors = []
const warnings = []
const information = []
if (image.indexOf("{") >= 0) {
information.push("Ignoring image with { in the path: " + image)
return {result: image}
}
if (image === "assets/SocialImage.png") {
return {result: image}
}
if (image.match(/[a-z]*/)) {
if (Svg.All[image + ".svg"] !== undefined) {
// This is a builtin img, e.g. 'checkmark' or 'crosshair'
return {result: image};
}
}
if (this._knownImagePaths !== undefined && !this._knownImagePaths.has(image)) {
if(DoesImageExist.doesPathExist === undefined){
errors.push(`Image with path ${image} not found or not attributed; it is used in ${context}`)
}else if(!DoesImageExist.doesPathExist(image)){
errors.push(`Image with path ${image} does not exist; it is used in ${context}.\n Check for typo's and missing directories in the path.`)
}else{
errors.push(`Image with path ${image} is not attributed (but it exists); execute 'npm run query:licenses' to add the license information and/or run 'npm run generate:licenses' to compile all the license info`)
}
}
return {
result: image,
errors, warnings, information
}
}
}
class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
/**
* The paths where this layer is originally saved. Triggers some extra checks
@ -54,13 +101,14 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
private readonly knownImagePaths: Set<string>;
private readonly _isBuiltin: boolean;
private _sharedTagRenderings: Map<string, any>;
private readonly _validateImage : DesugaringStep<string>;
constructor(knownImagePaths: Set<string>, path: string, isBuiltin: boolean, sharedTagRenderings: Map<string, any>) {
super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme");
this.knownImagePaths = knownImagePaths;
this._path = path;
this._isBuiltin = isBuiltin;
this._sharedTagRenderings = sharedTagRenderings;
this._validateImage = new DoesImageExist(this.knownImagePaths);
}
convert(json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors: string[], warnings: string[], information: string[] } {
@ -89,26 +137,7 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
errors.push("Found a remote image: " + remoteImage + " in theme " + json.id + ", please download it.")
}
for (const image of images) {
if (image.indexOf("{") >= 0) {
information.push("Ignoring image with { in the path: " + image)
continue
}
if (image === "assets/SocialImage.png") {
continue
}
if (image.match(/[a-z]*/)) {
if (Svg.All[image + ".svg"] !== undefined) {
// This is a builtin img, e.g. 'checkmark' or 'crosshair'
continue;// =>
}
}
if (this.knownImagePaths !== undefined && !this.knownImagePaths.has(image)) {
const ctx = context === undefined ? "" : ` in a layer defined in the theme ${context}`
errors.push(`Image with path ${image} not found or not attributed; it is used in ${json.id}${ctx}`)
}
this._validateImage.convertJoin(image, context === undefined ? "" : ` in a layer defined in the theme ${context}`, errors, warnings, information)
}
if (json.icon.endsWith(".svg")) {
@ -433,8 +462,11 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ
for (const image of images) {
if (this.knownImagePaths !== undefined && !this.knownImagePaths.has(image)) {
const closeNames = Utils.sortedByLevenshteinDistance(image, Array.from(this.knownImagePaths), i => i);
const ctx = context === undefined ? "" : ` in a layer defined in the theme ${context}`
errors.push(`Image with path ${image} not found or not attributed; it is used in ${json.id}${ctx}`)
errors.push(`Image with path ${image} not found or not attributed; it is used in ${json.id}${ctx}.\n Did you mean one of ${closeNames.slice(0, 3).join(", ")}`)
}
}

View file

@ -5,6 +5,7 @@ import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson";
import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
import Constants from "../Models/Constants";
import {
DoesImageExist,
PrevalidateTheme,
ValidateLayer,
ValidateTagRenderings,
@ -152,6 +153,8 @@ class LayerOverviewUtils {
main(_: string[]) {
DoesImageExist.doesPathExist = (path) => existsSync(path)
const licensePaths = new Set<string>()
for (const i in licenses) {
licensePaths.add(licenses[i].path)
@ -261,7 +264,6 @@ class LayerOverviewUtils {
tagRenderings: this.getSharedTagRenderings(knownImagePaths),
publicLayers
}
const nonDefaultLanguages : {theme: string, language: string}[] = []
for (const themeInfo of themeFiles) {
let themeFile = themeInfo.parsed
const themePath = themeInfo.path