forked from MapComplete/MapComplete
		
	Better errors when an image is missing
This commit is contained in:
		
							parent
							
								
									494bc28ddf
								
							
						
					
					
						commit
						f27b60f80d
					
				
					 3 changed files with 65 additions and 23 deletions
				
			
		| 
						 | 
					@ -39,6 +39,14 @@ export abstract class Conversion<TIn, TOut> {
 | 
				
			||||||
        return DesugaringStep.strict(fixed)
 | 
					        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>{
 | 
					    public andThenF<X>(f: (tout:TOut) => X ): Conversion<TIn, X>{
 | 
				
			||||||
        return new Pipe(
 | 
					        return new Pipe(
 | 
				
			||||||
            this,
 | 
					            this,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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> {
 | 
					class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The paths where this layer is originally saved. Triggers some extra checks
 | 
					     * 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 knownImagePaths: Set<string>;
 | 
				
			||||||
    private readonly _isBuiltin: boolean;
 | 
					    private readonly _isBuiltin: boolean;
 | 
				
			||||||
    private _sharedTagRenderings: Map<string, any>;
 | 
					    private _sharedTagRenderings: Map<string, any>;
 | 
				
			||||||
 | 
					    private readonly _validateImage : DesugaringStep<string>;
 | 
				
			||||||
    constructor(knownImagePaths: Set<string>, path: string, isBuiltin: boolean, sharedTagRenderings: Map<string, any>) {
 | 
					    constructor(knownImagePaths: Set<string>, path: string, isBuiltin: boolean, sharedTagRenderings: Map<string, any>) {
 | 
				
			||||||
        super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme");
 | 
					        super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme");
 | 
				
			||||||
        this.knownImagePaths = knownImagePaths;
 | 
					        this.knownImagePaths = knownImagePaths;
 | 
				
			||||||
        this._path = path;
 | 
					        this._path = path;
 | 
				
			||||||
        this._isBuiltin = isBuiltin;
 | 
					        this._isBuiltin = isBuiltin;
 | 
				
			||||||
        this._sharedTagRenderings = sharedTagRenderings;
 | 
					        this._sharedTagRenderings = sharedTagRenderings;
 | 
				
			||||||
 | 
					        this._validateImage = new DoesImageExist(this.knownImagePaths);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    convert(json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors: string[], warnings: string[], information: string[] } {
 | 
					    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.")
 | 
					                errors.push("Found a remote image: " + remoteImage + " in theme " + json.id + ", please download it.")
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            for (const image of images) {
 | 
					            for (const image of images) {
 | 
				
			||||||
                if (image.indexOf("{") >= 0) {
 | 
					                this._validateImage.convertJoin(image, context === undefined ? "" : ` in a layer defined in the theme ${context}`, errors, warnings, information)
 | 
				
			||||||
                    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}`)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (json.icon.endsWith(".svg")) {
 | 
					            if (json.icon.endsWith(".svg")) {
 | 
				
			||||||
| 
						 | 
					@ -433,8 +462,11 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    for (const image of images) {
 | 
					                    for (const image of images) {
 | 
				
			||||||
                        if (this.knownImagePaths !== undefined && !this.knownImagePaths.has(image)) {
 | 
					                        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}`
 | 
					                            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(", ")}`)
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    
 | 
					                    
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,6 +5,7 @@ import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson";
 | 
				
			||||||
import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
 | 
					import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
 | 
				
			||||||
import Constants from "../Models/Constants";
 | 
					import Constants from "../Models/Constants";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					    DoesImageExist,
 | 
				
			||||||
    PrevalidateTheme,
 | 
					    PrevalidateTheme,
 | 
				
			||||||
    ValidateLayer,
 | 
					    ValidateLayer,
 | 
				
			||||||
    ValidateTagRenderings,
 | 
					    ValidateTagRenderings,
 | 
				
			||||||
| 
						 | 
					@ -152,6 +153,8 @@ class LayerOverviewUtils {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    main(_: string[]) {
 | 
					    main(_: string[]) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        DoesImageExist.doesPathExist = (path) => existsSync(path)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        const licensePaths = new Set<string>()
 | 
					        const licensePaths = new Set<string>()
 | 
				
			||||||
        for (const i in licenses) {
 | 
					        for (const i in licenses) {
 | 
				
			||||||
            licensePaths.add(licenses[i].path)
 | 
					            licensePaths.add(licenses[i].path)
 | 
				
			||||||
| 
						 | 
					@ -261,7 +264,6 @@ class LayerOverviewUtils {
 | 
				
			||||||
            tagRenderings: this.getSharedTagRenderings(knownImagePaths),
 | 
					            tagRenderings: this.getSharedTagRenderings(knownImagePaths),
 | 
				
			||||||
            publicLayers
 | 
					            publicLayers
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const nonDefaultLanguages : {theme: string, language: string}[] = []
 | 
					 | 
				
			||||||
        for (const themeInfo of themeFiles) {
 | 
					        for (const themeInfo of themeFiles) {
 | 
				
			||||||
            let themeFile = themeInfo.parsed
 | 
					            let themeFile = themeInfo.parsed
 | 
				
			||||||
            const themePath = themeInfo.path
 | 
					            const themePath = themeInfo.path
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue