diff --git a/Models/ThemeConfig/Conversion/Validation.ts b/Models/ThemeConfig/Conversion/Validation.ts index 47250c36be..103b8efd39 100644 --- a/Models/ThemeConfig/Conversion/Validation.ts +++ b/Models/ThemeConfig/Conversion/Validation.ts @@ -45,14 +45,15 @@ class ValidateLanguageCompleteness extends DesugaringStep { } } -export class DoesImageExist extends DesugaringStep{ - +export class DoesImageExist extends DesugaringStep { + private readonly _knownImagePaths: Set; - public static doesPathExist : (path: string) => boolean = undefined; - - constructor(knownImagePaths: Set) { + private readonly doesPathExist: (path: string) => boolean = undefined; + + constructor(knownImagePaths: Set, checkExistsSync: (path: string) => boolean = undefined) { super("Checks if an image exists", [], "DoesImageExist"); this._knownImagePaths = knownImagePaths; + this.doesPathExist = checkExistsSync; } convert(image: string, context: string): { result: string; errors?: string[]; warnings?: string[]; information?: string[] } { @@ -76,11 +77,11 @@ export class DoesImageExist extends DesugaringStep{ } if (this._knownImagePaths !== undefined && !this._knownImagePaths.has(image)) { - if(DoesImageExist.doesPathExist === undefined){ + if (this.doesPathExist === undefined) { errors.push(`Image with path ${image} not found or not attributed; it is used in ${context}`) - }else if(!DoesImageExist.doesPathExist(image)){ + } else if (!this.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{ + } 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`) } } @@ -89,7 +90,7 @@ export class DoesImageExist extends DesugaringStep{ errors, warnings, information } } - + } class ValidateTheme extends DesugaringStep { @@ -98,17 +99,16 @@ class ValidateTheme extends DesugaringStep { * @private */ private readonly _path?: string; - private readonly knownImagePaths: Set; private readonly _isBuiltin: boolean; private _sharedTagRenderings: Map; - private readonly _validateImage : DesugaringStep; - constructor(knownImagePaths: Set, path: string, isBuiltin: boolean, sharedTagRenderings: Map) { + private readonly _validateImage: DesugaringStep; + + constructor(doesImageExist: DoesImageExist, path: string, isBuiltin: boolean, sharedTagRenderings: Map) { super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme"); - this.knownImagePaths = knownImagePaths; + this._validateImage = doesImageExist; 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[] } { @@ -179,9 +179,7 @@ class ValidateTheme extends DesugaringStep { if (theme.id !== filename) { errors.push("Theme ids should be the same as the name.json, but we got id: " + theme.id + " and filename " + filename + " (" + this._path + ")") } - if (!this.knownImagePaths.has(theme.icon)) { - errors.push("The theme image " + theme.icon + " is not attributed or not saved locally") - } + this._validateImage.convertJoin(theme.icon, context + ".icon", errors, warnings, information); const dups = Utils.Dupiclates(json.layers.map(layer => layer["id"])) if (dups.length > 0) { errors.push(`The theme ${json.id} defines multiple layers with id ${dups.join(", ")}`) @@ -195,16 +193,16 @@ class ValidateTheme extends DesugaringStep { // The first key in the the title-field must be english, otherwise the title in the loading page will be the different language const targetLanguage = theme.title.SupportedLanguages()[0] - if(targetLanguage !== "en"){ + if (targetLanguage !== "en") { warnings.push(`TargetLanguage is not 'en' for public theme ${theme.id}, it is ${targetLanguage}. Move 'en' up in the title of the theme and set it as the first key`) } - + // Official, public themes must have a full english translation const checked = new ValidateLanguageCompleteness("en") .convert(theme, theme.id) errors.push(...checked.errors) - - + + } } catch (e) { @@ -221,10 +219,10 @@ class ValidateTheme extends DesugaringStep { } export class ValidateThemeAndLayers extends Fuse { - constructor(knownImagePaths: Set, path: string, isBuiltin: boolean, sharedTagRenderings: Map) { + constructor(doesImageExist: DoesImageExist, path: string, isBuiltin: boolean, sharedTagRenderings: Map) { super("Validates a theme and the contained layers", - new ValidateTheme(knownImagePaths, path, isBuiltin, sharedTagRenderings), - new On("layers", new Each(new ValidateLayer(undefined, false, knownImagePaths))) + new ValidateTheme(doesImageExist, path, isBuiltin, sharedTagRenderings), + new On("layers", new Each(new ValidateLayer(undefined, false, doesImageExist))) ); } } @@ -383,7 +381,7 @@ export class DetectShadowedMappings extends DesugaringStep { - private knownImagePaths: Set; - constructor(knownImagePaths: Set) { + private readonly _doesImageExist: DoesImageExist; + + constructor(doesImageExist: DoesImageExist) { super("Checks that 'then'clauses in mappings don't have images, but use 'icon' instead", [], "DetectMappingsWithImages"); - this.knownImagePaths = knownImagePaths; + this._doesImageExist = doesImageExist; } /** @@ -441,9 +440,9 @@ export class DetectMappingsWithImages extends DesugaringStep msg.indexOf("./assets/layers/bike_parking/staple.svg") >= 0) // => true */ convert(json: TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[], information?: string[] } { - const errors = [] - const warnings = [] - const information = [] + const errors: string[] = [] + const warnings: string[] = [] + const information: string[] = [] if (json.mappings === undefined || json.mappings.length === 0) { return {result: json} } @@ -461,15 +460,10 @@ export class DetectMappingsWithImages extends DesugaringStep 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}.\n Did you mean one of ${closeNames.slice(0, 3).join(", ")}`) - } + this._doesImageExist.convertJoin(image, ctx, errors, warnings, information); + } - + } } else if (ignore) { warnings.push(`${ctx}: unused '${ignoreToken}' - please remove this`) @@ -486,10 +480,10 @@ export class DetectMappingsWithImages extends DesugaringStep { - constructor(layerConfig?: LayerConfigJson, knownImagePaths?: Set) { + constructor(layerConfig?: LayerConfigJson, doesImageExist?: DoesImageExist) { super("Various validation on tagRenderingConfigs", new DetectShadowedMappings(layerConfig), - new DetectMappingsWithImages(knownImagePaths) + new DetectMappingsWithImages(doesImageExist) ); } } @@ -501,13 +495,13 @@ export class ValidateLayer extends DesugaringStep { */ private readonly _path?: string; private readonly _isBuiltin: boolean; - private knownImagePaths: Set | undefined; + private readonly _doesImageExist: DoesImageExist; - constructor(path: string, isBuiltin: boolean, knownImagePaths: Set) { + constructor(path: string, isBuiltin: boolean, doesImageExist: DoesImageExist) { super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer"); this._path = path; this._isBuiltin = isBuiltin; - this.knownImagePaths = knownImagePaths + this._doesImageExist = doesImageExist } convert(json: LayerConfigJson, context: string): { result: LayerConfigJson; errors: string[]; warnings?: string[], information?: string[] } { @@ -595,7 +589,7 @@ export class ValidateLayer extends DesugaringStep { } } if (json.tagRenderings !== undefined) { - const r = new On("tagRenderings", new Each(new ValidateTagRenderings(json, this.knownImagePaths))).convert(json, context) + const r = new On("tagRenderings", new Each(new ValidateTagRenderings(json, this._doesImageExist))).convert(json, context) warnings.push(...(r.warnings ?? [])) errors.push(...(r.errors ?? [])) information.push(...(r.information ?? [])) diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index 9a8d762e8c..866155db83 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -83,10 +83,10 @@ class LayerOverviewUtils { writeFileSync(`./assets/generated/layers/${layer.id}.json`, JSON.stringify(layer, null, " "), "UTF8"); } - getSharedTagRenderings(knownImagePaths: Set): Map { + getSharedTagRenderings(doesImageExist: DoesImageExist): Map { const dict = new Map(); - const validator = new ValidateTagRenderings(undefined, knownImagePaths); + const validator = new ValidateTagRenderings(undefined, doesImageExist); for (const key in questions["default"]) { if (key === "id") { continue @@ -153,15 +153,13 @@ class LayerOverviewUtils { main(_: string[]) { - DoesImageExist.doesPathExist = (path) => existsSync(path) - const licensePaths = new Set() for (const i in licenses) { licensePaths.add(licenses[i].path) } - - const sharedLayers = this.buildLayerIndex(licensePaths); - const sharedThemes = this.buildThemeIndex(licensePaths, sharedLayers) + const doesImageExist = new DoesImageExist(licensePaths, existsSync) + const sharedLayers = this.buildLayerIndex(doesImageExist); + const sharedThemes = this.buildThemeIndex(doesImageExist, sharedLayers) writeFileSync("./assets/generated/known_layers_and_themes.json", JSON.stringify({ "layers": Array.from(sharedLayers.values()), @@ -191,12 +189,12 @@ class LayerOverviewUtils { console.log(green("All done!")) } - private buildLayerIndex(knownImagePaths: Set): Map { + private buildLayerIndex(doesImageExist: DoesImageExist): Map { // First, we expand and validate all builtin layers. These are written to assets/generated/layers // At the same time, an index of available layers is built. console.log(" ---------- VALIDATING BUILTIN LAYERS ---------") - const sharedTagRenderings = this.getSharedTagRenderings(knownImagePaths); + const sharedTagRenderings = this.getSharedTagRenderings(doesImageExist); const layerFiles = ScriptUtils.getLayerFiles(); const sharedLayers = new Map() const state: DesugaringContext = { @@ -212,7 +210,7 @@ class LayerOverviewUtils { fixed.source.osmTags = {"and": [fixed.source.osmTags]} } - const validator = new ValidateLayer(sharedLayerJson.path, true, knownImagePaths); + const validator = new ValidateLayer(sharedLayerJson.path, true, doesImageExist); validator.convertStrict(fixed, context) if (sharedLayers.has(fixed.id)) { @@ -252,7 +250,7 @@ class LayerOverviewUtils { return publicLayerIds } - private buildThemeIndex(knownImagePaths: Set, sharedLayers: Map): Map { + private buildThemeIndex(doesImageExist: DoesImageExist, sharedLayers: Map): Map { console.log(" ---------- VALIDATING BUILTIN THEMES ---------") const themeFiles = ScriptUtils.getThemeFiles(); const fixed = new Map(); @@ -261,7 +259,7 @@ class LayerOverviewUtils { const convertState: DesugaringContext = { sharedLayers, - tagRenderings: this.getSharedTagRenderings(knownImagePaths), + tagRenderings: this.getSharedTagRenderings(doesImageExist), publicLayers } for (const themeInfo of themeFiles) { @@ -273,10 +271,7 @@ class LayerOverviewUtils { themeFile = new PrepareTheme(convertState).convertStrict(themeFile, themePath) - if (knownImagePaths === undefined) { - throw "Could not load known images/licenses" - } - new ValidateThemeAndLayers(knownImagePaths, themePath, true, convertState.tagRenderings) + new ValidateThemeAndLayers(doesImageExist, themePath, true, convertState.tagRenderings) .convertStrict(themeFile, themePath) this.writeTheme(themeFile)